Showing the changes to java/pkg/java/text (120 of 18352 files). Show all
Unified · Split
Binary file not shown.
| @@ -0,0 +1,84 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1997, 2002, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | package java.text; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * An Annotation object is used as a wrapper for a text attribute value if | ||
| 30 | * the attribute has annotation characteristics. These characteristics are: | ||
| 31 | * <ul> | ||
| 32 | * <li>The text range that the attribute is applied to is critical to the | ||
| 33 | * semantics of the range. That means, the attribute cannot be applied to subranges | ||
| 34 | * of the text range that it applies to, and, if two adjacent text ranges have | ||
| 35 | * the same value for this attribute, the attribute still cannot be applied to | ||
| 36 | * the combined range as a whole with this value. | ||
| 37 | * <li>The attribute or its value usually do no longer apply if the underlying text is | ||
| 38 | * changed. | ||
| 39 | * </ul> | ||
| 40 | * | ||
| 41 | * An example is grammatical information attached to a sentence: | ||
| 42 | * For the previous sentence, you can say that "an example" | ||
| 43 | * is the subject, but you cannot say the same about "an", "example", or "exam". | ||
| 44 | * When the text is changed, the grammatical information typically becomes invalid. | ||
| 45 | * Another example is Japanese reading information (yomi). | ||
| 46 | * | ||
| 47 | * <p> | ||
| 48 | * Wrapping the attribute value into an Annotation object guarantees that | ||
| 49 | * adjacent text runs don't get merged even if the attribute values are equal, | ||
| 50 | * and indicates to text containers that the attribute should be discarded if | ||
| 51 | * the underlying text is modified. | ||
| 52 | * | ||
| 53 | * @see AttributedCharacterIterator | ||
| 54 | * @since 1.2 | ||
| 55 | */ | ||
| 56 | |||
| 57 | public class Annotation { | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Constructs an annotation record with the given value, which | ||
| 61 | * may be null. | ||
| 62 | * @param value The value of the attribute | ||
| 63 | */ | ||
| 64 | public Annotation(Object value) { | ||
| 65 | this.value = value; | ||
| 66 | } | ||
| 67 | |||
| 68 | /** | ||
| 69 | * Returns the value of the attribute, which may be null. | ||
| 70 | */ | ||
| 71 | public Object getValue() { | ||
| 72 | return value; | ||
| 73 | } | ||
| 74 | |||
| 75 | /** | ||
| 76 | * Returns the String representation of this Annotation. | ||
| 77 | */ | ||
| 78 | public String toString() { | ||
| 79 | return getClass().getName() + "[value=" + value + "]"; | ||
| 80 | } | ||
| 81 | |||
| 82 | private Object value; | ||
| 83 | |||
| 84 | }; | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,254 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | package java.text; | ||
| 27 | |||
| 28 | import java.io.InvalidObjectException; | ||
| 29 | import java.io.Serializable; | ||
| 30 | import java.util.HashMap; | ||
| 31 | import java.util.Map; | ||
| 32 | import java.util.Set; | ||
| 33 | |||
| 34 | /** | ||
| 35 | * An {@code AttributedCharacterIterator} allows iteration through both text and | ||
| 36 | * related attribute information. | ||
| 37 | * | ||
| 38 | * <p> | ||
| 39 | * An attribute is a key/value pair, identified by the key. No two | ||
| 40 | * attributes on a given character can have the same key. | ||
| 41 | * | ||
| 42 | * <p>The values for an attribute are immutable, or must not be mutated | ||
| 43 | * by clients or storage. They are always passed by reference, and not | ||
| 44 | * cloned. | ||
| 45 | * | ||
| 46 | * <p>A <em>run with respect to an attribute</em> is a maximum text range for | ||
| 47 | * which: | ||
| 48 | * <ul> | ||
| 49 | * <li>the attribute is undefined or {@code null} for the entire range, or | ||
| 50 | * <li>the attribute value is defined and has the same non-{@code null} value for the | ||
| 51 | * entire range. | ||
| 52 | * </ul> | ||
| 53 | * | ||
| 54 | * <p>A <em>run with respect to a set of attributes</em> is a maximum text range for | ||
| 55 | * which this condition is met for each member attribute. | ||
| 56 | * | ||
| 57 | * <p>When getting a run with no explicit attributes specified (i.e., | ||
| 58 | * calling {@link #getRunStart()} and {@link #getRunLimit()}), any | ||
| 59 | * contiguous text segments having the same attributes (the same set | ||
| 60 | * of attribute/value pairs) are treated as separate runs if the | ||
| 61 | * attributes have been given to those text segments separately. | ||
| 62 | * | ||
| 63 | * <p>The returned indexes are limited to the range of the iterator. | ||
| 64 | * | ||
| 65 | * <p>The returned attribute information is limited to runs that contain | ||
| 66 | * the current character. | ||
| 67 | * | ||
| 68 | * <p> | ||
| 69 | * Attribute keys are instances of {@link AttributedCharacterIterator.Attribute} and its | ||
| 70 | * subclasses, such as {@link java.awt.font.TextAttribute}. | ||
| 71 | * | ||
| 72 | * @see AttributedCharacterIterator.Attribute | ||
| 73 | * @see java.awt.font.TextAttribute | ||
| 74 | * @see AttributedString | ||
| 75 | * @see Annotation | ||
| 76 | * @since 1.2 | ||
| 77 | */ | ||
| 78 | |||
| 79 | public interface AttributedCharacterIterator extends CharacterIterator { | ||
| 80 | |||
| 81 | /** | ||
| 82 | * Defines attribute keys that are used to identify text attributes. These | ||
| 83 | * keys are used in {@code AttributedCharacterIterator} and {@code AttributedString}. | ||
| 84 | * @see AttributedCharacterIterator | ||
| 85 | * @see AttributedString | ||
| 86 | * @since 1.2 | ||
| 87 | */ | ||
| 88 | |||
| 89 | public static class Attribute implements Serializable { | ||
| 90 | |||
| 91 | /** | ||
| 92 | * The name of this {@code Attribute}. The name is used primarily by {@code readResolve} | ||
| 93 | * to look up the corresponding predefined instance when deserializing | ||
| 94 | * an instance. | ||
| 95 | * @serial | ||
| 96 | */ | ||
| 97 | private String name; | ||
| 98 | |||
| 99 | // table of all instances in this class, used by readResolve | ||
| 100 | private static final Map instanceMap = new HashMap(7); | ||
| 101 | |||
| 102 | /** | ||
| 103 | * Constructs an {@code Attribute} with the given name. | ||
| 104 | */ | ||
| 105 | protected Attribute(String name) { | ||
| 106 | this.name = name; | ||
| 107 | if (this.getClass() == Attribute.class) { | ||
| 108 | instanceMap.put(name, this); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | /** | ||
| 113 | * Compares two objects for equality. This version only returns true | ||
| 114 | * for <code>x.equals(y)</code> if <code>x</code> and <code>y</code> refer | ||
| 115 | * to the same object, and guarantees this for all subclasses. | ||
| 116 | */ | ||
| 117 | public final boolean equals(Object obj) { | ||
| 118 | return super.equals(obj); | ||
| 119 | } | ||
| 120 | |||
| 121 | /** | ||
| 122 | * Returns a hash code value for the object. This version is identical to | ||
| 123 | * the one in {@code Object}, but is also final. | ||
| 124 | */ | ||
| 125 | public final int hashCode() { | ||
| 126 | return super.hashCode(); | ||
| 127 | } | ||
| 128 | |||
| 129 | /** | ||
| 130 | * Returns a string representation of the object. This version returns the | ||
| 131 | * concatenation of class name, {@code "("}, a name identifying the attribute | ||
| 132 | * and {@code ")"}. | ||
| 133 | */ | ||
| 134 | public String toString() { | ||
| 135 | return getClass().getName() + "(" + name + ")"; | ||
| 136 | } | ||
| 137 | |||
| 138 | /** | ||
| 139 | * Returns the name of the attribute. | ||
| 140 | */ | ||
| 141 | protected String getName() { | ||
| 142 | return name; | ||
| 143 | } | ||
| 144 | |||
| 145 | /** | ||
| 146 | * Resolves instances being deserialized to the predefined constants. | ||
| 147 | */ | ||
| 148 | protected Object readResolve() throws InvalidObjectException { | ||
| 149 | if (this.getClass() != Attribute.class) { | ||
| 150 | throw new InvalidObjectException("subclass didn't correctly implement readResolve"); | ||
| 151 | } | ||
| 152 | |||
| 153 | Attribute instance = (Attribute) instanceMap.get(getName()); | ||
| 154 | if (instance != null) { | ||
| 155 | return instance; | ||
| 156 | } else { | ||
| 157 | throw new InvalidObjectException("unknown attribute name"); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | /** | ||
| 162 | * Attribute key for the language of some text. | ||
| 163 | * <p> Values are instances of {@link java.util.Locale Locale}. | ||
| 164 | * @see java.util.Locale | ||
| 165 | */ | ||
| 166 | public static final Attribute LANGUAGE = new Attribute("language"); | ||
| 167 | |||
| 168 | /** | ||
| 169 | * Attribute key for the reading of some text. In languages where the written form | ||
| 170 | * and the pronunciation of a word are only loosely related (such as Japanese), | ||
| 171 | * it is often necessary to store the reading (pronunciation) along with the | ||
| 172 | * written form. | ||
| 173 | * <p>Values are instances of {@link Annotation} holding instances of {@link String}. | ||
| 174 | * @see Annotation | ||
| 175 | * @see java.lang.String | ||
| 176 | */ | ||
| 177 | public static final Attribute READING = new Attribute("reading"); | ||
| 178 | |||
| 179 | /** | ||
| 180 | * Attribute key for input method segments. Input methods often break | ||
| 181 | * up text into segments, which usually correspond to words. | ||
| 182 | * <p>Values are instances of {@link Annotation} holding a {@code null} reference. | ||
| 183 | * @see Annotation | ||
| 184 | */ | ||
| 185 | public static final Attribute INPUT_METHOD_SEGMENT = new Attribute("input_method_segment"); | ||
| 186 | |||
| 187 | // make sure the serial version doesn't change between compiler versions | ||
| 188 | private static final long serialVersionUID = -9142742483513960612L; | ||
| 189 | |||
| 190 | }; | ||
| 191 | |||
| 192 | /** | ||
| 193 | * Returns the index of the first character of the run | ||
| 194 | * with respect to all attributes containing the current character. | ||
| 195 | * | ||
| 196 | * <p>Any contiguous text segments having the same attributes (the | ||
| 197 | * same set of attribute/value pairs) are treated as separate runs | ||
| 198 | * if the attributes have been given to those text segments separately. | ||
| 199 | */ | ||
| 200 | public int getRunStart(); | ||
| 201 | |||
| 202 | /** | ||
| 203 | * Returns the index of the first character of the run | ||
| 204 | * with respect to the given {@code attribute} containing the current character. | ||
| 205 | */ | ||
| 206 | public int getRunStart(Attribute attribute); | ||
| 207 | |||
| 208 | /** | ||
| 209 | * Returns the index of the first character of the run | ||
| 210 | * with respect to the given {@code attributes} containing the current character. | ||
| 211 | */ | ||
| 212 | public int getRunStart(Set<? extends Attribute> attributes); | ||
| 213 | |||
| 214 | /** | ||
| 215 | * Returns the index of the first character following the run | ||
| 216 | * with respect to all attributes containing the current character. | ||
| 217 | * | ||
| 218 | * <p>Any contiguous text segments having the same attributes (the | ||
| 219 | * same set of attribute/value pairs) are treated as separate runs | ||
| 220 | * if the attributes have been given to those text segments separately. | ||
| 221 | */ | ||
| 222 | public int getRunLimit(); | ||
| 223 | |||
| 224 | /** | ||
| 225 | * Returns the index of the first character following the run | ||
| 226 | * with respect to the given {@code attribute} containing the current character. | ||
| 227 | */ | ||
| 228 | public int getRunLimit(Attribute attribute); | ||
| 229 | |||
| 230 | /** | ||
| 231 | * Returns the index of the first character following the run | ||
| 232 | * with respect to the given {@code attributes} containing the current character. | ||
| 233 | */ | ||
| 234 | public int getRunLimit(Set<? extends Attribute> attributes); | ||
| 235 | |||
| 236 | /** | ||
| 237 | * Returns a map with the attributes defined on the current | ||
| 238 | * character. | ||
| 239 | */ | ||
| 240 | public Map<Attribute,Object> getAttributes(); | ||
| 241 | |||
| 242 | /** | ||
| 243 | * Returns the value of the named {@code attribute} for the current character. | ||
| 244 | * Returns {@code null} if the {@code attribute} is not defined. | ||
| 245 | */ | ||
| 246 | public Object getAttribute(Attribute attribute); | ||
| 247 | |||
| 248 | /** | ||
| 249 | * Returns the keys of all attributes defined on the | ||
| 250 | * iterator's text range. The set is empty if no | ||
| 251 | * attributes are defined. | ||
| 252 | */ | ||
| 253 | public Set<Attribute> getAllAttributeKeys(); | ||
| 254 | }; | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,1120 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | package java.text; | ||
| 27 | |||
| 28 | import java.util.*; | ||
| 29 | import java.text.AttributedCharacterIterator.Attribute; | ||
| 30 | |||
| 31 | /** | ||
| 32 | * An AttributedString holds text and related attribute information. It | ||
| 33 | * may be used as the actual data storage in some cases where a text | ||
| 34 | * reader wants to access attributed text through the AttributedCharacterIterator | ||
| 35 | * interface. | ||
| 36 | * | ||
| 37 | * <p> | ||
| 38 | * An attribute is a key/value pair, identified by the key. No two | ||
| 39 | * attributes on a given character can have the same key. | ||
| 40 | * | ||
| 41 | * <p>The values for an attribute are immutable, or must not be mutated | ||
| 42 | * by clients or storage. They are always passed by reference, and not | ||
| 43 | * cloned. | ||
| 44 | * | ||
| 45 | * @see AttributedCharacterIterator | ||
| 46 | * @see Annotation | ||
| 47 | * @since 1.2 | ||
| 48 | */ | ||
| 49 | |||
| 50 | public class AttributedString { | ||
| 51 | |||
| 52 | // since there are no vectors of int, we have to use arrays. | ||
| 53 | // We allocate them in chunks of 10 elements so we don't have to allocate all the time. | ||
| 54 | private static final int ARRAY_SIZE_INCREMENT = 10; | ||
| 55 | |||
| 56 | // field holding the text | ||
| 57 | String text; | ||
| 58 | |||
| 59 | // fields holding run attribute information | ||
| 60 | // run attributes are organized by run | ||
| 61 | int runArraySize; // current size of the arrays | ||
| 62 | int runCount; // actual number of runs, <= runArraySize | ||
| 63 | int runStarts[]; // start index for each run | ||
| 64 | Vector runAttributes[]; // vector of attribute keys for each run | ||
| 65 | Vector runAttributeValues[]; // parallel vector of attribute values for each run | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Constructs an AttributedString instance with the given | ||
| 69 | * AttributedCharacterIterators. | ||
| 70 | * | ||
| 71 | * @param iterators AttributedCharacterIterators to construct | ||
| 72 | * AttributedString from. | ||
| 73 | * @throws NullPointerException if iterators is null | ||
| 74 | */ | ||
| 75 | AttributedString(AttributedCharacterIterator[] iterators) { | ||
| 76 | if (iterators == null) { | ||
| 77 | throw new NullPointerException("Iterators must not be null"); | ||
| 78 | } | ||
| 79 | if (iterators.length == 0) { | ||
| 80 | text = ""; | ||
| 81 | } | ||
| 82 | else { | ||
| 83 | // Build the String contents | ||
| 84 | StringBuffer buffer = new StringBuffer(); | ||
| 85 | for (int counter = 0; counter < iterators.length; counter++) { | ||
| 86 | appendContents(buffer, iterators[counter]); | ||
| 87 | } | ||
| 88 | |||
| 89 | text = buffer.toString(); | ||
| 90 | |||
| 91 | if (text.length() > 0) { | ||
| 92 | // Determine the runs, creating a new run when the attributes | ||
| 93 | // differ. | ||
| 94 | int offset = 0; | ||
| 95 | Map last = null; | ||
| 96 | |||
| 97 | for (int counter = 0; counter < iterators.length; counter++) { | ||
| 98 | AttributedCharacterIterator iterator = iterators[counter]; | ||
| 99 | int start = iterator.getBeginIndex(); | ||
| 100 | int end = iterator.getEndIndex(); | ||
| 101 | int index = start; | ||
| 102 | |||
| 103 | while (index < end) { | ||
| 104 | iterator.setIndex(index); | ||
| 105 | |||
| 106 | Map attrs = iterator.getAttributes(); | ||
| 107 | |||
| 108 | if (mapsDiffer(last, attrs)) { | ||
| 109 | setAttributes(attrs, index - start + offset); | ||
| 110 | } | ||
| 111 | last = attrs; | ||
| 112 | index = iterator.getRunLimit(); | ||
| 113 | } | ||
| 114 | offset += (end - start); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | /** | ||
| 121 | * Constructs an AttributedString instance with the given text. | ||
| 122 | * @param text The text for this attributed string. | ||
| 123 | * @exception NullPointerException if <code>text</code> is null. | ||
| 124 | */ | ||
| 125 | public AttributedString(String text) { | ||
| 126 | if (text == null) { | ||
| 127 | throw new NullPointerException(); | ||
| 128 | } | ||
| 129 | this.text = text; | ||
| 130 | } | ||
| 131 | |||
| 132 | /** | ||
| 133 | * Constructs an AttributedString instance with the given text and attributes. | ||
| 134 | * @param text The text for this attributed string. | ||
| 135 | * @param attributes The attributes that apply to the entire string. | ||
| 136 | * @exception NullPointerException if <code>text</code> or | ||
| 137 | * <code>attributes</code> is null. | ||
| 138 | * @exception IllegalArgumentException if the text has length 0 | ||
| 139 | * and the attributes parameter is not an empty Map (attributes | ||
| 140 | * cannot be applied to a 0-length range). | ||
| 141 | */ | ||
| 142 | public AttributedString(String text, | ||
| 143 | Map<? extends Attribute, ?> attributes) | ||
| 144 | { | ||
| 145 | if (text == null || attributes == null) { | ||
| 146 | throw new NullPointerException(); | ||
| 147 | } | ||
| 148 | this.text = text; | ||
| 149 | |||
| 150 | if (text.length() == 0) { | ||
| 151 | if (attributes.isEmpty()) | ||
| 152 | return; | ||
| 153 | throw new IllegalArgumentException("Can't add attribute to 0-length text"); | ||
| 154 | } | ||
| 155 | |||
| 156 | int attributeCount = attributes.size(); | ||
| 157 | if (attributeCount > 0) { | ||
| 158 | createRunAttributeDataVectors(); | ||
| 159 | Vector newRunAttributes = new Vector(attributeCount); | ||
| 160 | Vector newRunAttributeValues = new Vector(attributeCount); | ||
| 161 | runAttributes[0] = newRunAttributes; | ||
| 162 | runAttributeValues[0] = newRunAttributeValues; | ||
| 163 | Iterator iterator = attributes.entrySet().iterator(); | ||
| 164 | while (iterator.hasNext()) { | ||
| 165 | Map.Entry entry = (Map.Entry) iterator.next(); | ||
| 166 | newRunAttributes.addElement(entry.getKey()); | ||
| 167 | newRunAttributeValues.addElement(entry.getValue()); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | /** | ||
| 173 | * Constructs an AttributedString instance with the given attributed | ||
| 174 | * text represented by AttributedCharacterIterator. | ||
| 175 | * @param text The text for this attributed string. | ||
| 176 | * @exception NullPointerException if <code>text</code> is null. | ||
| 177 | */ | ||
| 178 | public AttributedString(AttributedCharacterIterator text) { | ||
| 179 | // If performance is critical, this constructor should be | ||
| 180 | // implemented here rather than invoking the constructor for a | ||
| 181 | // subrange. We can avoid some range checking in the loops. | ||
| 182 | this(text, text.getBeginIndex(), text.getEndIndex(), null); | ||
| 183 | } | ||
| 184 | |||
| 185 | /** | ||
| 186 | * Constructs an AttributedString instance with the subrange of | ||
| 187 | * the given attributed text represented by | ||
| 188 | * AttributedCharacterIterator. If the given range produces an | ||
| 189 | * empty text, all attributes will be discarded. Note that any | ||
| 190 | * attributes wrapped by an Annotation object are discarded for a | ||
| 191 | * subrange of the original attribute range. | ||
| 192 | * | ||
| 193 | * @param text The text for this attributed string. | ||
| 194 | * @param beginIndex Index of the first character of the range. | ||
| 195 | * @param endIndex Index of the character following the last character | ||
| 196 | * of the range. | ||
| 197 | * @exception NullPointerException if <code>text</code> is null. | ||
| 198 | * @exception IllegalArgumentException if the subrange given by | ||
| 199 | * beginIndex and endIndex is out of the text range. | ||
| 200 | * @see java.text.Annotation | ||
| 201 | */ | ||
| 202 | public AttributedString(AttributedCharacterIterator text, | ||
| 203 | int beginIndex, | ||
| 204 | int endIndex) { | ||
| 205 | this(text, beginIndex, endIndex, null); | ||
| 206 | } | ||
| 207 | |||
| 208 | /** | ||
| 209 | * Constructs an AttributedString instance with the subrange of | ||
| 210 | * the given attributed text represented by | ||
| 211 | * AttributedCharacterIterator. Only attributes that match the | ||
| 212 | * given attributes will be incorporated into the instance. If the | ||
| 213 | * given range produces an empty text, all attributes will be | ||
| 214 | * discarded. Note that any attributes wrapped by an Annotation | ||
| 215 | * object are discarded for a subrange of the original attribute | ||
| 216 | * range. | ||
| 217 | * | ||
| 218 | * @param text The text for this attributed string. | ||
| 219 | * @param beginIndex Index of the first character of the range. | ||
| 220 | * @param endIndex Index of the character following the last character | ||
| 221 | * of the range. | ||
| 222 | * @param attributes Specifies attributes to be extracted | ||
| 223 | * from the text. If null is specified, all available attributes will | ||
| 224 | * be used. | ||
| 225 | * @exception NullPointerException if <code>text</code> is null. | ||
| 226 | * @exception IllegalArgumentException if the subrange given by | ||
| 227 | * beginIndex and endIndex is out of the text range. | ||
| 228 | * @see java.text.Annotation | ||
| 229 | */ | ||
| 230 | public AttributedString(AttributedCharacterIterator text, | ||
| 231 | int beginIndex, | ||
| 232 | int endIndex, | ||
| 233 | Attribute[] attributes) { | ||
| 234 | if (text == null) { | ||
| 235 | throw new NullPointerException(); | ||
| 236 | } | ||
| 237 | |||
| 238 | // Validate the given subrange | ||
| 239 | int textBeginIndex = text.getBeginIndex(); | ||
| 240 | int textEndIndex = text.getEndIndex(); | ||
| 241 | if (beginIndex < textBeginIndex || endIndex > textEndIndex || beginIndex > endIndex) | ||
| 242 | throw new IllegalArgumentException("Invalid substring range"); | ||
| 243 | |||
| 244 | // Copy the given string | ||
| 245 | StringBuffer textBuffer = new StringBuffer(); | ||
| 246 | text.setIndex(beginIndex); | ||
| 247 | for (char c = text.current(); text.getIndex() < endIndex; c = text.next()) | ||
| 248 | textBuffer.append(c); | ||
| 249 | this.text = textBuffer.toString(); | ||
| 250 | |||
| 251 | if (beginIndex == endIndex) | ||
| 252 | return; | ||
| 253 | |||
| 254 | // Select attribute keys to be taken care of | ||
| 255 | HashSet keys = new HashSet(); | ||
| 256 | if (attributes == null) { | ||
| 257 | keys.addAll(text.getAllAttributeKeys()); | ||
| 258 | } else { | ||
| 259 | for (int i = 0; i < attributes.length; i++) | ||
| 260 | keys.add(attributes[i]); | ||
| 261 | keys.retainAll(text.getAllAttributeKeys()); | ||
| 262 | } | ||
| 263 | if (keys.isEmpty()) | ||
| 264 | return; | ||
| 265 | |||
| 266 | // Get and set attribute runs for each attribute name. Need to | ||
| 267 | // scan from the top of the text so that we can discard any | ||
| 268 | // Annotation that is no longer applied to a subset text segment. | ||
| 269 | Iterator itr = keys.iterator(); | ||
| 270 | while (itr.hasNext()) { | ||
| 271 | Attribute attributeKey = (Attribute)itr.next(); | ||
| 272 | text.setIndex(textBeginIndex); | ||
| 273 | while (text.getIndex() < endIndex) { | ||
| 274 | int start = text.getRunStart(attributeKey); | ||
| 275 | int limit = text.getRunLimit(attributeKey); | ||
| 276 | Object value = text.getAttribute(attributeKey); | ||
| 277 | |||
| 278 | if (value != null) { | ||
| 279 | if (value instanceof Annotation) { | ||
| 280 | if (start >= beginIndex && limit <= endIndex) { | ||
| 281 | addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex); | ||
| 282 | } else { | ||
| 283 | if (limit > endIndex) | ||
| 284 | break; | ||
| 285 | } | ||
| 286 | } else { | ||
| 287 | // if the run is beyond the given (subset) range, we | ||
| 288 | // don't need to process further. | ||
| 289 | if (start >= endIndex) | ||
| 290 | break; | ||
| 291 | if (limit > beginIndex) { | ||
| 292 | // attribute is applied to any subrange | ||
| 293 | if (start < beginIndex) | ||
| 294 | start = beginIndex; | ||
| 295 | if (limit > endIndex) | ||
| 296 | limit = endIndex; | ||
| 297 | if (start != limit) { | ||
| 298 | addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex); | ||
| 299 | } | ||
| 300 | } | ||
| 301 | } | ||
| 302 | } | ||
| 303 | text.setIndex(limit); | ||
| 304 | } | ||
| 305 | } | ||
| 306 | } | ||
| 307 | |||
| 308 | /** | ||
| 309 | * Adds an attribute to the entire string. | ||
| 310 | * @param attribute the attribute key | ||
| 311 | * @param value the value of the attribute; may be null | ||
| 312 | * @exception NullPointerException if <code>attribute</code> is null. | ||
| 313 | * @exception IllegalArgumentException if the AttributedString has length 0 | ||
| 314 | * (attributes cannot be applied to a 0-length range). | ||
| 315 | */ | ||
| 316 | public void addAttribute(Attribute attribute, Object value) { | ||
| 317 | |||
| 318 | if (attribute == null) { | ||
| 319 | throw new NullPointerException(); | ||
| 320 | } | ||
| 321 | |||
| 322 | int len = length(); | ||
| 323 | if (len == 0) { | ||
| 324 | throw new IllegalArgumentException("Can't add attribute to 0-length text"); | ||
| 325 | } | ||
| 326 | |||
| 327 | addAttributeImpl(attribute, value, 0, len); | ||
| 328 | } | ||
| 329 | |||
| 330 | /** | ||
| 331 | * Adds an attribute to a subrange of the string. | ||
| 332 | * @param attribute the attribute key | ||
| 333 | * @param value The value of the attribute. May be null. | ||
| 334 | * @param beginIndex Index of the first character of the range. | ||
| 335 | * @param endIndex Index of the character following the last character of the range. | ||
| 336 | * @exception NullPointerException if <code>attribute</code> is null. | ||
| 337 | * @exception IllegalArgumentException if beginIndex is less then 0, endIndex is | ||
| 338 | * greater than the length of the string, or beginIndex and endIndex together don't | ||
| 339 | * define a non-empty subrange of the string. | ||
| 340 | */ | ||
| 341 | public void addAttribute(Attribute attribute, Object value, | ||
| 342 | int beginIndex, int endIndex) { | ||
| 343 | |||
| 344 | if (attribute == null) { | ||
| 345 | throw new NullPointerException(); | ||
| 346 | } | ||
| 347 | |||
| 348 | if (beginIndex < 0 || endIndex > length() || beginIndex >= endIndex) { | ||
| 349 | throw new IllegalArgumentException("Invalid substring range"); | ||
| 350 | } | ||
| 351 | |||
| 352 | addAttributeImpl(attribute, value, beginIndex, endIndex); | ||
| 353 | } | ||
| 354 | |||
| 355 | /** | ||
| 356 | * Adds a set of attributes to a subrange of the string. | ||
| 357 | * @param attributes The attributes to be added to the string. | ||
| 358 | * @param beginIndex Index of the first character of the range. | ||
| 359 | * @param endIndex Index of the character following the last | ||
| 360 | * character of the range. | ||
| 361 | * @exception NullPointerException if <code>attributes</code> is null. | ||
| 362 | * @exception IllegalArgumentException if beginIndex is less then | ||
| 363 | * 0, endIndex is greater than the length of the string, or | ||
| 364 | * beginIndex and endIndex together don't define a non-empty | ||
| 365 | * subrange of the string and the attributes parameter is not an | ||
| 366 | * empty Map. | ||
| 367 | */ | ||
| 368 | public void addAttributes(Map<? extends Attribute, ?> attributes, | ||
| 369 | int beginIndex, int endIndex) | ||
| 370 | { | ||
| 371 | if (attributes == null) { | ||
| 372 | throw new NullPointerException(); | ||
| 373 | } | ||
| 374 | |||
| 375 | if (beginIndex < 0 || endIndex > length() || beginIndex > endIndex) { | ||
| 376 | throw new IllegalArgumentException("Invalid substring range"); | ||
| 377 | } | ||
| 378 | if (beginIndex == endIndex) { | ||
| 379 | if (attributes.isEmpty()) | ||
| 380 | return; | ||
| 381 | throw new IllegalArgumentException("Can't add attribute to 0-length text"); | ||
| 382 | } | ||
| 383 | |||
| 384 | // make sure we have run attribute data vectors | ||
| 385 | if (runCount == 0) { | ||
| 386 | createRunAttributeDataVectors(); | ||
| 387 | } | ||
| 388 | |||
| 389 | // break up runs if necessary | ||
| 390 | int beginRunIndex = ensureRunBreak(beginIndex); | ||
| 391 | int endRunIndex = ensureRunBreak(endIndex); | ||
| 392 | |||
| 393 | Iterator iterator = attributes.entrySet().iterator(); | ||
| 394 | while (iterator.hasNext()) { | ||
| 395 | Map.Entry entry = (Map.Entry) iterator.next(); | ||
| 396 | addAttributeRunData((Attribute) entry.getKey(), entry.getValue(), beginRunIndex, endRunIndex); | ||
| 397 | } | ||
| 398 | } | ||
| 399 | |||
| 400 | private synchronized void addAttributeImpl(Attribute attribute, Object value, | ||
| 401 | int beginIndex, int endIndex) { | ||
| 402 | |||
| 403 | // make sure we have run attribute data vectors | ||
| 404 | if (runCount == 0) { | ||
| 405 | createRunAttributeDataVectors(); | ||
| 406 | } | ||
| 407 | |||
| 408 | // break up runs if necessary | ||
| 409 | int beginRunIndex = ensureRunBreak(beginIndex); | ||
| 410 | int endRunIndex = ensureRunBreak(endIndex); | ||
| 411 | |||
| 412 | addAttributeRunData(attribute, value, beginRunIndex, endRunIndex); | ||
| 413 | } | ||
| 414 | |||
| 415 | private final void createRunAttributeDataVectors() { | ||
| 416 | // use temporary variables so things remain consistent in case of an exception | ||
| 417 | int newRunStarts[] = new int[ARRAY_SIZE_INCREMENT]; | ||
| 418 | Vector newRunAttributes[] = new Vector[ARRAY_SIZE_INCREMENT]; | ||
| 419 | Vector newRunAttributeValues[] = new Vector[ARRAY_SIZE_INCREMENT]; | ||
| 420 | runStarts = newRunStarts; | ||
| 421 | runAttributes = newRunAttributes; | ||
| 422 | runAttributeValues = newRunAttributeValues; | ||
| 423 | runArraySize = ARRAY_SIZE_INCREMENT; | ||
| 424 | runCount = 1; // assume initial run starting at index 0 | ||
| 425 | } | ||
| 426 | |||
| 427 | // ensure there's a run break at offset, return the index of the run | ||
| 428 | private final int ensureRunBreak(int offset) { | ||
| 429 | return ensureRunBreak(offset, true); | ||
| 430 | } | ||
| 431 | |||
| 432 | /** | ||
| 433 | * Ensures there is a run break at offset, returning the index of | ||
| 434 | * the run. If this results in splitting a run, two things can happen: | ||
| 435 | * <ul> | ||
| 436 | * <li>If copyAttrs is true, the attributes from the existing run | ||
| 437 | * will be placed in both of the newly created runs. | ||
| 438 | * <li>If copyAttrs is false, the attributes from the existing run | ||
| 439 | * will NOT be copied to the run to the right (>= offset) of the break, | ||
| 440 | * but will exist on the run to the left (< offset). | ||
| 441 | * </ul> | ||
| 442 | */ | ||
| 443 | private final int ensureRunBreak(int offset, boolean copyAttrs) { | ||
| 444 | if (offset == length()) { | ||
| 445 | return runCount; | ||
| 446 | } | ||
| 447 | |||
| 448 | // search for the run index where this offset should be | ||
| 449 | int runIndex = 0; | ||
| 450 | while (runIndex < runCount && runStarts[runIndex] < offset) { | ||
| 451 | runIndex++; | ||
| 452 | } | ||
| 453 | |||
| 454 | // if the offset is at a run start already, we're done | ||
| 455 | if (runIndex < runCount && runStarts[runIndex] == offset) { | ||
| 456 | return runIndex; | ||
| 457 | } | ||
| 458 | |||
| 459 | // we'll have to break up a run | ||
| 460 | // first, make sure we have enough space in our arrays | ||
| 461 | if (runCount == runArraySize) { | ||
| 462 | int newArraySize = runArraySize + ARRAY_SIZE_INCREMENT; | ||
| 463 | int newRunStarts[] = new int[newArraySize]; | ||
| 464 | Vector newRunAttributes[] = new Vector[newArraySize]; | ||
| 465 | Vector newRunAttributeValues[] = new Vector[newArraySize]; | ||
| 466 | for (int i = 0; i < runArraySize; i++) { | ||
| 467 | newRunStarts[i] = runStarts[i]; | ||
| 468 | newRunAttributes[i] = runAttributes[i]; | ||
| 469 | newRunAttributeValues[i] = runAttributeValues[i]; | ||
| 470 | } | ||
| 471 | runStarts = newRunStarts; | ||
| 472 | runAttributes = newRunAttributes; | ||
| 473 | runAttributeValues = newRunAttributeValues; | ||
| 474 | runArraySize = newArraySize; | ||
| 475 | } | ||
| 476 | |||
| 477 | // make copies of the attribute information of the old run that the new one used to be part of | ||
| 478 | // use temporary variables so things remain consistent in case of an exception | ||
| 479 | Vector newRunAttributes = null; | ||
| 480 | Vector newRunAttributeValues = null; | ||
| 481 | |||
| 482 | if (copyAttrs) { | ||
| 483 | Vector oldRunAttributes = runAttributes[runIndex - 1]; | ||
| 484 | Vector oldRunAttributeValues = runAttributeValues[runIndex - 1]; | ||
| 485 | if (oldRunAttributes != null) { | ||
| 486 | newRunAttributes = (Vector) oldRunAttributes.clone(); | ||
| 487 | } | ||
| 488 | if (oldRunAttributeValues != null) { | ||
| 489 | newRunAttributeValues = (Vector) oldRunAttributeValues.clone(); | ||
| 490 | } | ||
| 491 | } | ||
| 492 | |||
| 493 | // now actually break up the run | ||
| 494 | runCount++; | ||
| 495 | for (int i = runCount - 1; i > runIndex; i--) { | ||
| 496 | runStarts[i] = runStarts[i - 1]; | ||
| 497 | runAttributes[i] = runAttributes[i - 1]; | ||
| 498 | runAttributeValues[i] = runAttributeValues[i - 1]; | ||
| 499 | } | ||
| 500 | runStarts[runIndex] = offset; | ||
| 501 | runAttributes[runIndex] = newRunAttributes; | ||
| 502 | runAttributeValues[runIndex] = newRunAttributeValues; | ||
| 503 | |||
| 504 | return runIndex; | ||
| 505 | } | ||
| 506 | |||
| 507 | // add the attribute attribute/value to all runs where beginRunIndex <= runIndex < endRunIndex | ||
| 508 | private void addAttributeRunData(Attribute attribute, Object value, | ||
| 509 | int beginRunIndex, int endRunIndex) { | ||
| 510 | |||
| 511 | for (int i = beginRunIndex; i < endRunIndex; i++) { | ||
| 512 | int keyValueIndex = -1; // index of key and value in our vectors; assume we don't have an entry yet | ||
| 513 | if (runAttributes[i] == null) { | ||
| 514 | Vector newRunAttributes = new Vector(); | ||
| 515 | Vector newRunAttributeValues = new Vector(); | ||
| 516 | runAttributes[i] = newRunAttributes; | ||
| 517 | runAttributeValues[i] = newRunAttributeValues; | ||
| 518 | } else { | ||
| 519 | // check whether we have an entry already | ||
| 520 | keyValueIndex = runAttributes[i].indexOf(attribute); | ||
| 521 | } | ||
| 522 | |||
| 523 | if (keyValueIndex == -1) { | ||
| 524 | // create new entry | ||
| 525 | int oldSize = runAttributes[i].size(); | ||
| 526 | runAttributes[i].addElement(attribute); | ||
| 527 | try { | ||
| 528 | runAttributeValues[i].addElement(value); | ||
| 529 | } | ||
| 530 | catch (Exception e) { | ||
| 531 | runAttributes[i].setSize(oldSize); | ||
| 532 | runAttributeValues[i].setSize(oldSize); | ||
| 533 | } | ||
| 534 | } else { | ||
| 535 | // update existing entry | ||
| 536 | runAttributeValues[i].set(keyValueIndex, value); | ||
| 537 | } | ||
| 538 | } | ||
| 539 | } | ||
| 540 | |||
| 541 | /** | ||
| 542 | * Creates an AttributedCharacterIterator instance that provides access to the entire contents of | ||
| 543 | * this string. | ||
| 544 | * | ||
| 545 | * @return An iterator providing access to the text and its attributes. | ||
| 546 | */ | ||
| 547 | public AttributedCharacterIterator getIterator() { | ||
| 548 | return getIterator(null, 0, length()); | ||
| 549 | } | ||
| 550 | |||
| 551 | /** | ||
| 552 | * Creates an AttributedCharacterIterator instance that provides access to | ||
| 553 | * selected contents of this string. | ||
| 554 | * Information about attributes not listed in attributes that the | ||
| 555 | * implementor may have need not be made accessible through the iterator. | ||
| 556 | * If the list is null, all available attribute information should be made | ||
| 557 | * accessible. | ||
| 558 | * | ||
| 559 | * @param attributes a list of attributes that the client is interested in | ||
| 560 | * @return an iterator providing access to the entire text and its selected attributes | ||
| 561 | */ | ||
| 562 | public AttributedCharacterIterator getIterator(Attribute[] attributes) { | ||
| 563 | return getIterator(attributes, 0, length()); | ||
| 564 | } | ||
| 565 | |||
| 566 | /** | ||
| 567 | * Creates an AttributedCharacterIterator instance that provides access to | ||
| 568 | * selected contents of this string. | ||
| 569 | * Information about attributes not listed in attributes that the | ||
| 570 | * implementor may have need not be made accessible through the iterator. | ||
| 571 | * If the list is null, all available attribute information should be made | ||
| 572 | * accessible. | ||
| 573 | * | ||
| 574 | * @param attributes a list of attributes that the client is interested in | ||
| 575 | * @param beginIndex the index of the first character | ||
| 576 | * @param endIndex the index of the character following the last character | ||
| 577 | * @return an iterator providing access to the text and its attributes | ||
| 578 | * @exception IllegalArgumentException if beginIndex is less then 0, | ||
| 579 | * endIndex is greater than the length of the string, or beginIndex is | ||
| 580 | * greater than endIndex. | ||
| 581 | */ | ||
| 582 | public AttributedCharacterIterator getIterator(Attribute[] attributes, int beginIndex, int endIndex) { | ||
| 583 | return new AttributedStringIterator(attributes, beginIndex, endIndex); | ||
| 584 | } | ||
| 585 | |||
| 586 | // all (with the exception of length) reading operations are private, | ||
| 587 | // since AttributedString instances are accessed through iterators. | ||
| 588 | |||
| 589 | // length is package private so that CharacterIteratorFieldDelegate can | ||
| 590 | // access it without creating an AttributedCharacterIterator. | ||
| 591 | int length() { | ||
| 592 | return text.length(); | ||
| 593 | } | ||
| 594 | |||
| 595 | private char charAt(int index) { | ||
| 596 | return text.charAt(index); | ||
| 597 | } | ||
| 598 | |||
| 599 | private synchronized Object getAttribute(Attribute attribute, int runIndex) { | ||
| 600 | Vector currentRunAttributes = runAttributes[runIndex]; | ||
| 601 | Vector currentRunAttributeValues = runAttributeValues[runIndex]; | ||
| 602 | if (currentRunAttributes == null) { | ||
| 603 | return null; | ||
| 604 | } | ||
| 605 | int attributeIndex = currentRunAttributes.indexOf(attribute); | ||
| 606 | if (attributeIndex != -1) { | ||
| 607 | return currentRunAttributeValues.elementAt(attributeIndex); | ||
| 608 | } | ||
| 609 | else { | ||
| 610 | return null; | ||
| 611 | } | ||
| 612 | } | ||
| 613 | |||
| 614 | // gets an attribute value, but returns an annotation only if it's range does not extend outside the range beginIndex..endIndex | ||
| 615 | private Object getAttributeCheckRange(Attribute attribute, int runIndex, int beginIndex, int endIndex) { | ||
| 616 | Object value = getAttribute(attribute, runIndex); | ||
| 617 | if (value instanceof Annotation) { | ||
| 618 | // need to check whether the annotation's range extends outside the iterator's range | ||
| 619 | if (beginIndex > 0) { | ||
| 620 | int currIndex = runIndex; | ||
| 621 | int runStart = runStarts[currIndex]; | ||
| 622 | while (runStart >= beginIndex && | ||
| 623 | valuesMatch(value, getAttribute(attribute, currIndex - 1))) { | ||
| 624 | currIndex--; | ||
| 625 | runStart = runStarts[currIndex]; | ||
| 626 | } | ||
| 627 | if (runStart < beginIndex) { | ||
| 628 | // annotation's range starts before iterator's range | ||
| 629 | return null; | ||
| 630 | } | ||
| 631 | } | ||
| 632 | int textLength = length(); | ||
| 633 | if (endIndex < textLength) { | ||
| 634 | int currIndex = runIndex; | ||
| 635 | int runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength; | ||
| 636 | while (runLimit <= endIndex && | ||
| 637 | valuesMatch(value, getAttribute(attribute, currIndex + 1))) { | ||
| 638 | currIndex++; | ||
| 639 | runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength; | ||
| 640 | } | ||
| 641 | if (runLimit > endIndex) { | ||
| 642 | // annotation's range ends after iterator's range | ||
| 643 | return null; | ||
| 644 | } | ||
| 645 | } | ||
| 646 | // annotation's range is subrange of iterator's range, | ||
| 647 | // so we can return the value | ||
| 648 | } | ||
| 649 | return value; | ||
| 650 | } | ||
| 651 | |||
| 652 | // returns whether all specified attributes have equal values in the runs with the given indices | ||
| 653 | private boolean attributeValuesMatch(Set attributes, int runIndex1, int runIndex2) { | ||
| 654 | Iterator iterator = attributes.iterator(); | ||
| 655 | while (iterator.hasNext()) { | ||
| 656 | Attribute key = (Attribute) iterator.next(); | ||
| 657 | if (!valuesMatch(getAttribute(key, runIndex1), getAttribute(key, runIndex2))) { | ||
| 658 | return false; | ||
| 659 | } | ||
| 660 | } | ||
| 661 | return true; | ||
| 662 | } | ||
| 663 | |||
| 664 | // returns whether the two objects are either both null or equal | ||
| 665 | private final static boolean valuesMatch(Object value1, Object value2) { | ||
| 666 | if (value1 == null) { | ||
| 667 | return value2 == null; | ||
| 668 | } else { | ||
| 669 | return value1.equals(value2); | ||
| 670 | } | ||
| 671 | } | ||
| 672 | |||
| 673 | /** | ||
| 674 | * Appends the contents of the CharacterIterator iterator into the | ||
| 675 | * StringBuffer buf. | ||
| 676 | */ | ||
| 677 | private final void appendContents(StringBuffer buf, | ||
| 678 | CharacterIterator iterator) { | ||
| 679 | int index = iterator.getBeginIndex(); | ||
| 680 | int end = iterator.getEndIndex(); | ||
| 681 | |||
| 682 | while (index < end) { | ||
| 683 | iterator.setIndex(index++); | ||
| 684 | buf.append(iterator.current()); | ||
| 685 | } | ||
| 686 | } | ||
| 687 | |||
| 688 | /** | ||
| 689 | * Sets the attributes for the range from offset to the next run break | ||
| 690 | * (typically the end of the text) to the ones specified in attrs. | ||
| 691 | * This is only meant to be called from the constructor! | ||
| 692 | */ | ||
| 693 | private void setAttributes(Map attrs, int offset) { | ||
| 694 | if (runCount == 0) { | ||
| 695 | createRunAttributeDataVectors(); | ||
| 696 | } | ||
| 697 | |||
| 698 | int index = ensureRunBreak(offset, false); | ||
| 699 | int size; | ||
| 700 | |||
| 701 | if (attrs != null && (size = attrs.size()) > 0) { | ||
| 702 | Vector runAttrs = new Vector(size); | ||
| 703 | Vector runValues = new Vector(size); | ||
| 704 | Iterator iterator = attrs.entrySet().iterator(); | ||
| 705 | |||
| 706 | while (iterator.hasNext()) { | ||
| 707 | Map.Entry entry = (Map.Entry)iterator.next(); | ||
| 708 | |||
| 709 | runAttrs.add(entry.getKey()); | ||
| 710 | runValues.add(entry.getValue()); | ||
| 711 | } | ||
| 712 | runAttributes[index] = runAttrs; | ||
| 713 | runAttributeValues[index] = runValues; | ||
| 714 | } | ||
| 715 | } | ||
| 716 | |||
| 717 | /** | ||
| 718 | * Returns true if the attributes specified in last and attrs differ. | ||
| 719 | */ | ||
| 720 | private static boolean mapsDiffer(Map last, Map attrs) { | ||
| 721 | if (last == null) { | ||
| 722 | return (attrs != null && attrs.size() > 0); | ||
| 723 | } | ||
| 724 | return (!last.equals(attrs)); | ||
| 725 | } | ||
| 726 | |||
| 727 | |||
| 728 | // the iterator class associated with this string class | ||
| 729 | |||
| 730 | final private class AttributedStringIterator implements AttributedCharacterIterator { | ||
| 731 | |||
| 732 | // note on synchronization: | ||
| 733 | // we don't synchronize on the iterator, assuming that an iterator is only used in one thread. | ||
| 734 | // we do synchronize access to the AttributedString however, since it's more likely to be shared between threads. | ||
| 735 | |||
| 736 | // start and end index for our iteration | ||
| 737 | private int beginIndex; | ||
| 738 | private int endIndex; | ||
| 739 | |||
| 740 | // attributes that our client is interested in | ||
| 741 | private Attribute[] relevantAttributes; | ||
| 742 | |||
| 743 | // the current index for our iteration | ||
| 744 | // invariant: beginIndex <= currentIndex <= endIndex | ||
| 745 | private int currentIndex; | ||
| 746 | |||
| 747 | // information about the run that includes currentIndex | ||
| 748 | private int currentRunIndex; | ||
| 749 | private int currentRunStart; | ||
| 750 | private int currentRunLimit; | ||
| 751 | |||
| 752 | // constructor | ||
| 753 | AttributedStringIterator(Attribute[] attributes, int beginIndex, int endIndex) { | ||
| 754 | |||
| 755 | if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) { | ||
| 756 | throw new IllegalArgumentException("Invalid substring range"); | ||
| 757 | } | ||
| 758 | |||
| 759 | this.beginIndex = beginIndex; | ||
| 760 | this.endIndex = endIndex; | ||
| 761 | this.currentIndex = beginIndex; | ||
| 762 | updateRunInfo(); | ||
| 763 | if (attributes != null) { | ||
| 764 | relevantAttributes = (Attribute[]) attributes.clone(); | ||
| 765 | } | ||
| 766 | } | ||
| 767 | |||
| 768 | // Object methods. See documentation in that class. | ||
| 769 | |||
| 770 | public boolean equals(Object obj) { | ||
| 771 | if (this == obj) { | ||
| 772 | return true; | ||
| 773 | } | ||
| 774 | if (!(obj instanceof AttributedStringIterator)) { | ||
| 775 | return false; | ||
| 776 | } | ||
| 777 | |||
| 778 | AttributedStringIterator that = (AttributedStringIterator) obj; | ||
| 779 | |||
| 780 | if (AttributedString.this != that.getString()) | ||
| 781 | return false; | ||
| 782 | if (currentIndex != that.currentIndex || beginIndex != that.beginIndex || endIndex != that.endIndex) | ||
| 783 | return false; | ||
| 784 | return true; | ||
| 785 | } | ||
| 786 | |||
| 787 | public int hashCode() { | ||
| 788 | return text.hashCode() ^ currentIndex ^ beginIndex ^ endIndex; | ||
| 789 | } | ||
| 790 | |||
| 791 | public Object clone() { | ||
| 792 | try { | ||
| 793 | AttributedStringIterator other = (AttributedStringIterator) super.clone(); | ||
| 794 | return other; | ||
| 795 | } | ||
| 796 | catch (CloneNotSupportedException e) { | ||
| 797 | throw new InternalError(); | ||
| 798 | } | ||
| 799 | } | ||
| 800 | |||
| 801 | // CharacterIterator methods. See documentation in that interface. | ||
| 802 | |||
| 803 | public char first() { | ||
| 804 | return internalSetIndex(beginIndex); | ||
| 805 | } | ||
| 806 | |||
| 807 | public char last() { | ||
| 808 | if (endIndex == beginIndex) { | ||
| 809 | return internalSetIndex(endIndex); | ||
| 810 | } else { | ||
| 811 | return internalSetIndex(endIndex - 1); | ||
| 812 | } | ||
| 813 | } | ||
| 814 | |||
| 815 | public char current() { | ||
| 816 | if (currentIndex == endIndex) { | ||
| 817 | return DONE; | ||
| 818 | } else { | ||
| 819 | return charAt(currentIndex); | ||
| 820 | } | ||
| 821 | } | ||
| 822 | |||
| 823 | public char next() { | ||
| 824 | if (currentIndex < endIndex) { | ||
| 825 | return internalSetIndex(currentIndex + 1); | ||
| 826 | } | ||
| 827 | else { | ||
| 828 | return DONE; | ||
| 829 | } | ||
| 830 | } | ||
| 831 | |||
| 832 | public char previous() { | ||
| 833 | if (currentIndex > beginIndex) { | ||
| 834 | return internalSetIndex(currentIndex - 1); | ||
| 835 | } | ||
| 836 | else { | ||
| 837 | return DONE; | ||
| 838 | } | ||
| 839 | } | ||
| 840 | |||
| 841 | public char setIndex(int position) { | ||
| 842 | if (position < beginIndex || position > endIndex) | ||
| 843 | throw new IllegalArgumentException("Invalid index"); | ||
| 844 | return internalSetIndex(position); | ||
| 845 | } | ||
| 846 | |||
| 847 | public int getBeginIndex() { | ||
| 848 | return beginIndex; | ||
| 849 | } | ||
| 850 | |||
| 851 | public int getEndIndex() { | ||
| 852 | return endIndex; | ||
| 853 | } | ||
| 854 | |||
| 855 | public int getIndex() { | ||
| 856 | return currentIndex; | ||
| 857 | } | ||
| 858 | |||
| 859 | // AttributedCharacterIterator methods. See documentation in that interface. | ||
| 860 | |||
| 861 | public int getRunStart() { | ||
| 862 | return currentRunStart; | ||
| 863 | } | ||
| 864 | |||
| 865 | public int getRunStart(Attribute attribute) { | ||
| 866 | if (currentRunStart == beginIndex || currentRunIndex == -1) { | ||
| 867 | return currentRunStart; | ||
| 868 | } else { | ||
| 869 | Object value = getAttribute(attribute); | ||
| 870 | int runStart = currentRunStart; | ||
| 871 | int runIndex = currentRunIndex; | ||
| 872 | while (runStart > beginIndex && | ||
| 873 | valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex - 1))) { | ||
| 874 | runIndex--; | ||
| 875 | runStart = runStarts[runIndex]; | ||
| 876 | } | ||
| 877 | if (runStart < beginIndex) { | ||
| 878 | runStart = beginIndex; | ||
| 879 | } | ||
| 880 | return runStart; | ||
| 881 | } | ||
| 882 | } | ||
| 883 | |||
| 884 | public int getRunStart(Set<? extends Attribute> attributes) { | ||
| 885 | if (currentRunStart == beginIndex || currentRunIndex == -1) { | ||
| 886 | return currentRunStart; | ||
| 887 | } else { | ||
| 888 | int runStart = currentRunStart; | ||
| 889 | int runIndex = currentRunIndex; | ||
| 890 | while (runStart > beginIndex && | ||
| 891 | AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex - 1)) { | ||
| 892 | runIndex--; | ||
| 893 | runStart = runStarts[runIndex]; | ||
| 894 | } | ||
| 895 | if (runStart < beginIndex) { | ||
| 896 | runStart = beginIndex; | ||
| 897 | } | ||
| 898 | return runStart; | ||
| 899 | } | ||
| 900 | } | ||
| 901 | |||
| 902 | public int getRunLimit() { | ||
| 903 | return currentRunLimit; | ||
| 904 | } | ||
| 905 | |||
| 906 | public int getRunLimit(Attribute attribute) { | ||
| 907 | if (currentRunLimit == endIndex || currentRunIndex == -1) { | ||
| 908 | return currentRunLimit; | ||
| 909 | } else { | ||
| 910 | Object value = getAttribute(attribute); | ||
| 911 | int runLimit = currentRunLimit; | ||
| 912 | int runIndex = currentRunIndex; | ||
| 913 | while (runLimit < endIndex && | ||
| 914 | valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex + 1))) { | ||
| 915 | runIndex++; | ||
| 916 | runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex; | ||
| 917 | } | ||
| 918 | if (runLimit > endIndex) { | ||
| 919 | runLimit = endIndex; | ||
| 920 | } | ||
| 921 | return runLimit; | ||
| 922 | } | ||
| 923 | } | ||
| 924 | |||
| 925 | public int getRunLimit(Set<? extends Attribute> attributes) { | ||
| 926 | if (currentRunLimit == endIndex || currentRunIndex == -1) { | ||
| 927 | return currentRunLimit; | ||
| 928 | } else { | ||
| 929 | int runLimit = currentRunLimit; | ||
| 930 | int runIndex = currentRunIndex; | ||
| 931 | while (runLimit < endIndex && | ||
| 932 | AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex + 1)) { | ||
| 933 | runIndex++; | ||
| 934 | runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex; | ||
| 935 | } | ||
| 936 | if (runLimit > endIndex) { | ||
| 937 | runLimit = endIndex; | ||
| 938 | } | ||
| 939 | return runLimit; | ||
| 940 | } | ||
| 941 | } | ||
| 942 | |||
| 943 | public Map<Attribute,Object> getAttributes() { | ||
| 944 | if (runAttributes == null || currentRunIndex == -1 || runAttributes[currentRunIndex] == null) { | ||
| 945 | // ??? would be nice to return null, but current spec doesn't allow it | ||
| 946 | // returning Hashtable saves AttributeMap from dealing with emptiness | ||
| 947 | return new Hashtable(); | ||
| 948 | } | ||
| 949 | return new AttributeMap(currentRunIndex, beginIndex, endIndex); | ||
| 950 | } | ||
| 951 | |||
| 952 | public Set<Attribute> getAllAttributeKeys() { | ||
| 953 | // ??? This should screen out attribute keys that aren't relevant to the client | ||
| 954 | if (runAttributes == null) { | ||
| 955 | // ??? would be nice to return null, but current spec doesn't allow it | ||
| 956 | // returning HashSet saves us from dealing with emptiness | ||
| 957 | return new HashSet(); | ||
| 958 | } | ||
| 959 | synchronized (AttributedString.this) { | ||
| 960 | // ??? should try to create this only once, then update if necessary, | ||
| 961 | // and give callers read-only view | ||
| 962 | Set keys = new HashSet(); | ||
| 963 | int i = 0; | ||
| 964 | while (i < runCount) { | ||
| 965 | if (runStarts[i] < endIndex && (i == runCount - 1 || runStarts[i + 1] > beginIndex)) { | ||
| 966 | Vector currentRunAttributes = runAttributes[i]; | ||
| 967 | if (currentRunAttributes != null) { | ||
| 968 | int j = currentRunAttributes.size(); | ||
| 969 | while (j-- > 0) { | ||
| 970 | keys.add(currentRunAttributes.get(j)); | ||
| 971 | } | ||
| 972 | } | ||
| 973 | } | ||
| 974 | i++; | ||
| 975 | } | ||
| 976 | return keys; | ||
| 977 | } | ||
| 978 | } | ||
| 979 | |||
| 980 | public Object getAttribute(Attribute attribute) { | ||
| 981 | int runIndex = currentRunIndex; | ||
| 982 | if (runIndex < 0) { | ||
| 983 | return null; | ||
| 984 | } | ||
| 985 | return AttributedString.this.getAttributeCheckRange(attribute, runIndex, beginIndex, endIndex); | ||
| 986 | } | ||
| 987 | |||
| 988 | // internally used methods | ||
| 989 | |||
| 990 | private AttributedString getString() { | ||
| 991 | return AttributedString.this; | ||
| 992 | } | ||
| 993 | |||
| 994 | // set the current index, update information about the current run if necessary, | ||
| 995 | // return the character at the current index | ||
| 996 | private char internalSetIndex(int position) { | ||
| 997 | currentIndex = position; | ||
| 998 | if (position < currentRunStart || position >= currentRunLimit) { | ||
| 999 | updateRunInfo(); | ||
| 1000 | } | ||
| 1001 | if (currentIndex == endIndex) { | ||
| 1002 | return DONE; | ||
| 1003 | } else { | ||
| 1004 | return charAt(position); | ||
| 1005 | } | ||
| 1006 | } | ||
| 1007 | |||
| 1008 | // update the information about the current run | ||
| 1009 | private void updateRunInfo() { | ||
| 1010 | if (currentIndex == endIndex) { | ||
| 1011 | currentRunStart = currentRunLimit = endIndex; | ||
| 1012 | currentRunIndex = -1; | ||
| 1013 | } else { | ||
| 1014 | synchronized (AttributedString.this) { | ||
| 1015 | int runIndex = -1; | ||
| 1016 | while (runIndex < runCount - 1 && runStarts[runIndex + 1] <= currentIndex) | ||
| 1017 | runIndex++; | ||
| 1018 | currentRunIndex = runIndex; | ||
| 1019 | if (runIndex >= 0) { | ||
| 1020 | currentRunStart = runStarts[runIndex]; | ||
| 1021 | if (currentRunStart < beginIndex) | ||
| 1022 | currentRunStart = beginIndex; | ||
| 1023 | } | ||
| 1024 | else { | ||
| 1025 | currentRunStart = beginIndex; | ||
| 1026 | } | ||
| 1027 | if (runIndex < runCount - 1) { | ||
| 1028 | currentRunLimit = runStarts[runIndex + 1]; | ||
| 1029 | if (currentRunLimit > endIndex) | ||
| 1030 | currentRunLimit = endIndex; | ||
| 1031 | } | ||
| 1032 | else { | ||
| 1033 | currentRunLimit = endIndex; | ||
| 1034 | } | ||
| 1035 | } | ||
| 1036 | } | ||
| 1037 | } | ||
| 1038 | |||
| 1039 | } | ||
| 1040 | |||
| 1041 | // the map class associated with this string class, giving access to the attributes of one run | ||
| 1042 | |||
| 1043 | final private class AttributeMap extends AbstractMap<Attribute,Object> { | ||
| 1044 | |||
| 1045 | int runIndex; | ||
| 1046 | int beginIndex; | ||
| 1047 | int endIndex; | ||
| 1048 | |||
| 1049 | AttributeMap(int runIndex, int beginIndex, int endIndex) { | ||
| 1050 | this.runIndex = runIndex; | ||
| 1051 | this.beginIndex = beginIndex; | ||
| 1052 | this.endIndex = endIndex; | ||
| 1053 | } | ||
| 1054 | |||
| 1055 | public Set entrySet() { | ||
| 1056 | HashSet set = new HashSet(); | ||
| 1057 | synchronized (AttributedString.this) { | ||
| 1058 | int size = runAttributes[runIndex].size(); | ||
| 1059 | for (int i = 0; i < size; i++) { | ||
| 1060 | Attribute key = (Attribute) runAttributes[runIndex].get(i); | ||
| 1061 | Object value = runAttributeValues[runIndex].get(i); | ||
| 1062 | if (value instanceof Annotation) { | ||
| 1063 | value = AttributedString.this.getAttributeCheckRange(key, | ||
| 1064 | runIndex, beginIndex, endIndex); | ||
| 1065 | if (value == null) { | ||
| 1066 | continue; | ||
| 1067 | } | ||
| 1068 | } | ||
| 1069 | Map.Entry entry = new AttributeEntry(key, value); | ||
| 1070 | set.add(entry); | ||
| 1071 | } | ||
| 1072 | } | ||
| 1073 | return set; | ||
| 1074 | } | ||
| 1075 | |||
| 1076 | public Object get(Object key) { | ||
| 1077 | return AttributedString.this.getAttributeCheckRange((Attribute) key, runIndex, beginIndex, endIndex); | ||
| 1078 | } | ||
| 1079 | } | ||
| 1080 | } | ||
| 1081 | |||
| 1082 | class AttributeEntry implements Map.Entry { | ||
| 1083 | |||
| 1084 | private Attribute key; | ||
| 1085 | private Object value; | ||
| 1086 | |||
| 1087 | AttributeEntry(Attribute key, Object value) { | ||
| 1088 | this.key = key; | ||
| 1089 | this.value = value; | ||
| 1090 | } | ||
| 1091 | |||
| 1092 | public boolean equals(Object o) { | ||
| 1093 | if (!(o instanceof AttributeEntry)) { | ||
| 1094 | return false; | ||
| 1095 | } | ||
| 1096 | AttributeEntry other = (AttributeEntry) o; | ||
| 1097 | return other.key.equals(key) && | ||
| 1098 | (value == null ? other.value == null : other.value.equals(value)); | ||
| 1099 | } | ||
| 1100 | |||
| 1101 | public Object getKey() { | ||
| 1102 | return key; | ||
| 1103 | } | ||
| 1104 | |||
| 1105 | public Object getValue() { | ||
| 1106 | return value; | ||
| 1107 | } | ||
| 1108 | |||
| 1109 | public Object setValue(Object newValue) { | ||
| 1110 | throw new UnsupportedOperationException(); | ||
| 1111 | } | ||
| 1112 | |||
| 1113 | public int hashCode() { | ||
| 1114 | return key.hashCode() ^ (value==null ? 0 : value.hashCode()); | ||
| 1115 | } | ||
| 1116 | |||
| 1117 | public String toString() { | ||
| 1118 | return key.toString()+"="+value.toString(); | ||
| 1119 | } | ||
| 1120 | } | ||
Binary file not shown.
| @@ -0,0 +1,328 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright IBM Corp. 1999-2003 - All Rights Reserved | ||
| 28 | * | ||
| 29 | * The original version of this source code and documentation is | ||
| 30 | * copyrighted and owned by IBM. These materials are provided | ||
| 31 | * under terms of a License Agreement between IBM and Sun. | ||
| 32 | * This technology is protected by multiple US and International | ||
| 33 | * patents. This notice and attribution to IBM may not be removed. | ||
| 34 | */ | ||
| 35 | |||
| 36 | package java.text; | ||
| 37 | |||
| 38 | import sun.text.bidi.BidiBase; | ||
| 39 | |||
| 40 | /** | ||
| 41 | * This class implements the Unicode Bidirectional Algorithm. | ||
| 42 | * <p> | ||
| 43 | * A Bidi object provides information on the bidirectional reordering of the text | ||
| 44 | * used to create it. This is required, for example, to properly display Arabic | ||
| 45 | * or Hebrew text. These languages are inherently mixed directional, as they order | ||
| 46 | * numbers from left-to-right while ordering most other text from right-to-left. | ||
| 47 | * <p> | ||
| 48 | * Once created, a Bidi object can be queried to see if the text it represents is | ||
| 49 | * all left-to-right or all right-to-left. Such objects are very lightweight and | ||
| 50 | * this text is relatively easy to process. | ||
| 51 | * <p> | ||
| 52 | * If there are multiple runs of text, information about the runs can be accessed | ||
| 53 | * by indexing to get the start, limit, and level of a run. The level represents | ||
| 54 | * both the direction and the 'nesting level' of a directional run. Odd levels | ||
| 55 | * are right-to-left, while even levels are left-to-right. So for example level | ||
| 56 | * 0 represents left-to-right text, while level 1 represents right-to-left text, and | ||
| 57 | * level 2 represents left-to-right text embedded in a right-to-left run. | ||
| 58 | * | ||
| 59 | * @since 1.4 | ||
| 60 | */ | ||
| 61 | public final class Bidi { | ||
| 62 | |||
| 63 | /** Constant indicating base direction is left-to-right. */ | ||
| 64 | public static final int DIRECTION_LEFT_TO_RIGHT = 0; | ||
| 65 | |||
| 66 | /** Constant indicating base direction is right-to-left. */ | ||
| 67 | public static final int DIRECTION_RIGHT_TO_LEFT = 1; | ||
| 68 | |||
| 69 | /** | ||
| 70 | * Constant indicating that the base direction depends on the first strong | ||
| 71 | * directional character in the text according to the Unicode | ||
| 72 | * Bidirectional Algorithm. If no strong directional character is present, | ||
| 73 | * the base direction is left-to-right. | ||
| 74 | */ | ||
| 75 | public static final int DIRECTION_DEFAULT_LEFT_TO_RIGHT = -2; | ||
| 76 | |||
| 77 | /** | ||
| 78 | * Constant indicating that the base direction depends on the first strong | ||
| 79 | * directional character in the text according to the Unicode | ||
| 80 | * Bidirectional Algorithm. If no strong directional character is present, | ||
| 81 | * the base direction is right-to-left. | ||
| 82 | */ | ||
| 83 | public static final int DIRECTION_DEFAULT_RIGHT_TO_LEFT = -1; | ||
| 84 | |||
| 85 | private BidiBase bidiBase; | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Create Bidi from the given paragraph of text and base direction. | ||
| 89 | * @param paragraph a paragraph of text | ||
| 90 | * @param flags a collection of flags that control the algorithm. The | ||
| 91 | * algorithm understands the flags DIRECTION_LEFT_TO_RIGHT, DIRECTION_RIGHT_TO_LEFT, | ||
| 92 | * DIRECTION_DEFAULT_LEFT_TO_RIGHT, and DIRECTION_DEFAULT_RIGHT_TO_LEFT. | ||
| 93 | * Other values are reserved. | ||
| 94 | */ | ||
| 95 | public Bidi(String paragraph, int flags) { | ||
| 96 | if (paragraph == null) { | ||
| 97 | throw new IllegalArgumentException("paragraph is null"); | ||
| 98 | } | ||
| 99 | |||
| 100 | bidiBase = new BidiBase(paragraph.toCharArray(), 0, null, 0, paragraph.length(), flags); | ||
| 101 | } | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Create Bidi from the given paragraph of text. | ||
| 105 | * <p> | ||
| 106 | * The RUN_DIRECTION attribute in the text, if present, determines the base | ||
| 107 | * direction (left-to-right or right-to-left). If not present, the base | ||
| 108 | * direction is computes using the Unicode Bidirectional Algorithm, defaulting to left-to-right | ||
| 109 | * if there are no strong directional characters in the text. This attribute, if | ||
| 110 | * present, must be applied to all the text in the paragraph. | ||
| 111 | * <p> | ||
| 112 | * The BIDI_EMBEDDING attribute in the text, if present, represents embedding level | ||
| 113 | * information. Negative values from -1 to -62 indicate overrides at the absolute value | ||
| 114 | * of the level. Positive values from 1 to 62 indicate embeddings. Where values are | ||
| 115 | * zero or not defined, the base embedding level as determined by the base direction | ||
| 116 | * is assumed. | ||
| 117 | * <p> | ||
| 118 | * The NUMERIC_SHAPING attribute in the text, if present, converts European digits to | ||
| 119 | * other decimal digits before running the bidi algorithm. This attribute, if present, | ||
| 120 | * must be applied to all the text in the paragraph. | ||
| 121 | * | ||
| 122 | * @param paragraph a paragraph of text with optional character and paragraph attribute information | ||
| 123 | * | ||
| 124 | * @see java.awt.font.TextAttribute#BIDI_EMBEDDING | ||
| 125 | * @see java.awt.font.TextAttribute#NUMERIC_SHAPING | ||
| 126 | * @see java.awt.font.TextAttribute#RUN_DIRECTION | ||
| 127 | */ | ||
| 128 | public Bidi(AttributedCharacterIterator paragraph) { | ||
| 129 | if (paragraph == null) { | ||
| 130 | throw new IllegalArgumentException("paragraph is null"); | ||
| 131 | } | ||
| 132 | |||
| 133 | bidiBase = new BidiBase(0, 0); | ||
| 134 | bidiBase.setPara(paragraph); | ||
| 135 | } | ||
| 136 | |||
| 137 | /** | ||
| 138 | * Create Bidi from the given text, embedding, and direction information. | ||
| 139 | * The embeddings array may be null. If present, the values represent embedding level | ||
| 140 | * information. Negative values from -1 to -61 indicate overrides at the absolute value | ||
| 141 | * of the level. Positive values from 1 to 61 indicate embeddings. Where values are | ||
| 142 | * zero, the base embedding level as determined by the base direction is assumed. | ||
| 143 | * @param text an array containing the paragraph of text to process. | ||
| 144 | * @param textStart the index into the text array of the start of the paragraph. | ||
| 145 | * @param embeddings an array containing embedding values for each character in the paragraph. | ||
| 146 | * This can be null, in which case it is assumed that there is no external embedding information. | ||
| 147 | * @param embStart the index into the embedding array of the start of the paragraph. | ||
| 148 | * @param paragraphLength the length of the paragraph in the text and embeddings arrays. | ||
| 149 | * @param flags a collection of flags that control the algorithm. The | ||
| 150 | * algorithm understands the flags DIRECTION_LEFT_TO_RIGHT, DIRECTION_RIGHT_TO_LEFT, | ||
| 151 | * DIRECTION_DEFAULT_LEFT_TO_RIGHT, and DIRECTION_DEFAULT_RIGHT_TO_LEFT. | ||
| 152 | * Other values are reserved. | ||
| 153 | */ | ||
| 154 | public Bidi(char[] text, int textStart, byte[] embeddings, int embStart, int paragraphLength, int flags) { | ||
| 155 | if (text == null) { | ||
| 156 | throw new IllegalArgumentException("text is null"); | ||
| 157 | } | ||
| 158 | if (paragraphLength < 0) { | ||
| 159 | throw new IllegalArgumentException("bad length: " + paragraphLength); | ||
| 160 | } | ||
| 161 | if (textStart < 0 || paragraphLength > text.length - textStart) { | ||
| 162 | throw new IllegalArgumentException("bad range: " + textStart + | ||
| 163 | " length: " + paragraphLength + | ||
| 164 | " for text of length: " + text.length); | ||
| 165 | } | ||
| 166 | if (embeddings != null && (embStart < 0 || paragraphLength > embeddings.length - embStart)) { | ||
| 167 | throw new IllegalArgumentException("bad range: " + embStart + | ||
| 168 | " length: " + paragraphLength + | ||
| 169 | " for embeddings of length: " + text.length); | ||
| 170 | } | ||
| 171 | |||
| 172 | bidiBase = new BidiBase(text, textStart, embeddings, embStart, paragraphLength, flags); | ||
| 173 | } | ||
| 174 | |||
| 175 | /** | ||
| 176 | * Create a Bidi object representing the bidi information on a line of text within | ||
| 177 | * the paragraph represented by the current Bidi. This call is not required if the | ||
| 178 | * entire paragraph fits on one line. | ||
| 179 | * @param lineStart the offset from the start of the paragraph to the start of the line. | ||
| 180 | * @param lineLimit the offset from the start of the paragraph to the limit of the line. | ||
| 181 | */ | ||
| 182 | public Bidi createLineBidi(int lineStart, int lineLimit) { | ||
| 183 | AttributedString astr = new AttributedString(""); | ||
| 184 | Bidi newBidi = new Bidi(astr.getIterator()); | ||
| 185 | |||
| 186 | return bidiBase.setLine(this, bidiBase, newBidi, newBidi.bidiBase,lineStart, lineLimit); | ||
| 187 | } | ||
| 188 | |||
| 189 | /** | ||
| 190 | * Return true if the line is not left-to-right or right-to-left. This means it either has mixed runs of left-to-right | ||
| 191 | * and right-to-left text, or the base direction differs from the direction of the only run of text. | ||
| 192 | * @return true if the line is not left-to-right or right-to-left. | ||
| 193 | */ | ||
| 194 | public boolean isMixed() { | ||
| 195 | return bidiBase.isMixed(); | ||
| 196 | } | ||
| 197 | |||
| 198 | /** | ||
| 199 | * Return true if the line is all left-to-right text and the base direction is left-to-right. | ||
| 200 | * @return true if the line is all left-to-right text and the base direction is left-to-right | ||
| 201 | */ | ||
| 202 | public boolean isLeftToRight() { | ||
| 203 | return bidiBase.isLeftToRight(); | ||
| 204 | } | ||
| 205 | |||
| 206 | /** | ||
| 207 | * Return true if the line is all right-to-left text, and the base direction is right-to-left. | ||
| 208 | * @return true if the line is all right-to-left text, and the base direction is right-to-left | ||
| 209 | */ | ||
| 210 | public boolean isRightToLeft() { | ||
| 211 | return bidiBase.isRightToLeft(); | ||
| 212 | } | ||
| 213 | |||
| 214 | /** | ||
| 215 | * Return the length of text in the line. | ||
| 216 | * @return the length of text in the line | ||
| 217 | */ | ||
| 218 | public int getLength() { | ||
| 219 | return bidiBase.getLength(); | ||
| 220 | } | ||
| 221 | |||
| 222 | /** | ||
| 223 | * Return true if the base direction is left-to-right. | ||
| 224 | * @return true if the base direction is left-to-right | ||
| 225 | */ | ||
| 226 | public boolean baseIsLeftToRight() { | ||
| 227 | return bidiBase.baseIsLeftToRight(); | ||
| 228 | } | ||
| 229 | |||
| 230 | /** | ||
| 231 | * Return the base level (0 if left-to-right, 1 if right-to-left). | ||
| 232 | * @return the base level | ||
| 233 | */ | ||
| 234 | public int getBaseLevel() { | ||
| 235 | return bidiBase.getParaLevel(); | ||
| 236 | } | ||
| 237 | |||
| 238 | /** | ||
| 239 | * Return the resolved level of the character at offset. If offset is <0 or >= | ||
| 240 | * the length of the line, return the base direction level. | ||
| 241 | * @param offset the index of the character for which to return the level | ||
| 242 | * @return the resolved level of the character at offset | ||
| 243 | */ | ||
| 244 | public int getLevelAt(int offset) { | ||
| 245 | return bidiBase.getLevelAt(offset); | ||
| 246 | } | ||
| 247 | |||
| 248 | /** | ||
| 249 | * Return the number of level runs. | ||
| 250 | * @return the number of level runs | ||
| 251 | */ | ||
| 252 | public int getRunCount() { | ||
| 253 | return bidiBase.countRuns(); | ||
| 254 | } | ||
| 255 | |||
| 256 | /** | ||
| 257 | * Return the level of the nth logical run in this line. | ||
| 258 | * @param run the index of the run, between 0 and <code>getRunCount()</code> | ||
| 259 | * @return the level of the run | ||
| 260 | */ | ||
| 261 | public int getRunLevel(int run) { | ||
| 262 | return bidiBase.getRunLevel(run); | ||
| 263 | } | ||
| 264 | |||
| 265 | /** | ||
| 266 | * Return the index of the character at the start of the nth logical run in this line, as | ||
| 267 | * an offset from the start of the line. | ||
| 268 | * @param run the index of the run, between 0 and <code>getRunCount()</code> | ||
| 269 | * @return the start of the run | ||
| 270 | */ | ||
| 271 | public int getRunStart(int run) { | ||
| 272 | return bidiBase.getRunStart(run); | ||
| 273 | } | ||
| 274 | |||
| 275 | /** | ||
| 276 | * Return the index of the character past the end of the nth logical run in this line, as | ||
| 277 | * an offset from the start of the line. For example, this will return the length | ||
| 278 | * of the line for the last run on the line. | ||
| 279 | * @param run the index of the run, between 0 and <code>getRunCount()</code> | ||
| 280 | * @return limit the limit of the run | ||
| 281 | */ | ||
| 282 | public int getRunLimit(int run) { | ||
| 283 | return bidiBase.getRunLimit(run); | ||
| 284 | } | ||
| 285 | |||
| 286 | /** | ||
| 287 | * Return true if the specified text requires bidi analysis. If this returns false, | ||
| 288 | * the text will display left-to-right. Clients can then avoid constructing a Bidi object. | ||
| 289 | * Text in the Arabic Presentation Forms area of Unicode is presumed to already be shaped | ||
| 290 | * and ordered for display, and so will not cause this function to return true. | ||
| 291 | * | ||
| 292 | * @param text the text containing the characters to test | ||
| 293 | * @param start the start of the range of characters to test | ||
| 294 | * @param limit the limit of the range of characters to test | ||
| 295 | * @return true if the range of characters requires bidi analysis | ||
| 296 | */ | ||
| 297 | public static boolean requiresBidi(char[] text, int start, int limit) { | ||
| 298 | return BidiBase.requiresBidi(text, start, limit); | ||
| 299 | } | ||
| 300 | |||
| 301 | /** | ||
| 302 | * Reorder the objects in the array into visual order based on their levels. | ||
| 303 | * This is a utility function to use when you have a collection of objects | ||
| 304 | * representing runs of text in logical order, each run containing text | ||
| 305 | * at a single level. The elements at <code>index</code> from | ||
| 306 | * <code>objectStart</code> up to <code>objectStart + count</code> | ||
| 307 | * in the objects array will be reordered into visual order assuming | ||
| 308 | * each run of text has the level indicated by the corresponding element | ||
| 309 | * in the levels array (at <code>index - objectStart + levelStart</code>). | ||
| 310 | * | ||
| 311 | * @param levels an array representing the bidi level of each object | ||
| 312 | * @param levelStart the start position in the levels array | ||
| 313 | * @param objects the array of objects to be reordered into visual order | ||
| 314 | * @param objectStart the start position in the objects array | ||
| 315 | * @param count the number of objects to reorder | ||
| 316 | */ | ||
| 317 | public static void reorderVisually(byte[] levels, int levelStart, Object[] objects, int objectStart, int count) { | ||
| 318 | BidiBase.reorderVisually(levels, levelStart, objects, objectStart, count); | ||
| 319 | } | ||
| 320 | |||
| 321 | /** | ||
| 322 | * Display the bidi internal state, used in debugging. | ||
| 323 | */ | ||
| 324 | public String toString() { | ||
| 325 | return bidiBase.toString(); | ||
| 326 | } | ||
| 327 | |||
| 328 | } | ||
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,341 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1999, 2003, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * | ||
| 28 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 29 | * (C) Copyright IBM Corp. 1996 - 2002 - All Rights Reserved | ||
| 30 | * | ||
| 31 | * The original version of this source code and documentation | ||
| 32 | * is copyrighted and owned by Taligent, Inc., a wholly-owned | ||
| 33 | * subsidiary of IBM. These materials are provided under terms | ||
| 34 | * of a License Agreement between Taligent and Sun. This technology | ||
| 35 | * is protected by multiple US and International patents. | ||
| 36 | * | ||
| 37 | * This notice and attribution to Taligent may not be removed. | ||
| 38 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 39 | */ | ||
| 40 | package java.text; | ||
| 41 | |||
| 42 | import java.io.*; | ||
| 43 | import java.security.AccessController; | ||
| 44 | import java.security.PrivilegedActionException; | ||
| 45 | import java.security.PrivilegedExceptionAction; | ||
| 46 | import java.util.MissingResourceException; | ||
| 47 | import sun.text.CompactByteArray; | ||
| 48 | import sun.text.SupplementaryCharacterData; | ||
| 49 | |||
| 50 | /** | ||
| 51 | * This is the class that represents the list of known words used by | ||
| 52 | * DictionaryBasedBreakIterator. The conceptual data structure used | ||
| 53 | * here is a trie: there is a node hanging off the root node for every | ||
| 54 | * letter that can start a word. Each of these nodes has a node hanging | ||
| 55 | * off of it for every letter that can be the second letter of a word | ||
| 56 | * if this node is the first letter, and so on. The trie is represented | ||
| 57 | * as a two-dimensional array that can be treated as a table of state | ||
| 58 | * transitions. Indexes are used to compress this array, taking | ||
| 59 | * advantage of the fact that this array will always be very sparse. | ||
| 60 | */ | ||
| 61 | class BreakDictionary { | ||
| 62 | |||
| 63 | //========================================================================= | ||
| 64 | // data members | ||
| 65 | //========================================================================= | ||
| 66 | |||
| 67 | /** | ||
| 68 | * The version of the dictionary that was read in. | ||
| 69 | */ | ||
| 70 | private static int supportedVersion = 1; | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Maps from characters to column numbers. The main use of this is to | ||
| 74 | * avoid making room in the array for empty columns. | ||
| 75 | */ | ||
| 76 | private CompactByteArray columnMap = null; | ||
| 77 | private SupplementaryCharacterData supplementaryCharColumnMap = null; | ||
| 78 | |||
| 79 | /** | ||
| 80 | * The number of actual columns in the table | ||
| 81 | */ | ||
| 82 | private int numCols; | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Columns are organized into groups of 32. This says how many | ||
| 86 | * column groups. (We could calculate this, but we store the | ||
| 87 | * value to avoid having to repeatedly calculate it.) | ||
| 88 | */ | ||
| 89 | private int numColGroups; | ||
| 90 | |||
| 91 | /** | ||
| 92 | * The actual compressed state table. Each conceptual row represents | ||
| 93 | * a state, and the cells in it contain the row numbers of the states | ||
| 94 | * to transition to for each possible letter. 0 is used to indicate | ||
| 95 | * an illegal combination of letters (i.e., the error state). The | ||
| 96 | * table is compressed by eliminating all the unpopulated (i.e., zero) | ||
| 97 | * cells. Multiple conceptual rows can then be doubled up in a single | ||
| 98 | * physical row by sliding them up and possibly shifting them to one | ||
| 99 | * side or the other so the populated cells don't collide. Indexes | ||
| 100 | * are used to identify unpopulated cells and to locate populated cells. | ||
| 101 | */ | ||
| 102 | private short[] table = null; | ||
| 103 | |||
| 104 | /** | ||
| 105 | * This index maps logical row numbers to physical row numbers | ||
| 106 | */ | ||
| 107 | private short[] rowIndex = null; | ||
| 108 | |||
| 109 | /** | ||
| 110 | * A bitmap is used to tell which cells in the comceptual table are | ||
| 111 | * populated. This array contains all the unique bit combinations | ||
| 112 | * in that bitmap. If the table is more than 32 columns wide, | ||
| 113 | * successive entries in this array are used for a single row. | ||
| 114 | */ | ||
| 115 | private int[] rowIndexFlags = null; | ||
| 116 | |||
| 117 | /** | ||
| 118 | * This index maps from a logical row number into the bitmap table above. | ||
| 119 | * (This keeps us from storing duplicate bitmap combinations.) Since there | ||
| 120 | * are a lot of rows with only one populated cell, instead of wasting space | ||
| 121 | * in the bitmap table, we just store a negative number in this index for | ||
| 122 | * rows with one populated cell. The absolute value of that number is | ||
| 123 | * the column number of the populated cell. | ||
| 124 | */ | ||
| 125 | private short[] rowIndexFlagsIndex = null; | ||
| 126 | |||
| 127 | /** | ||
| 128 | * For each logical row, this index contains a constant that is added to | ||
| 129 | * the logical column number to get the physical column number | ||
| 130 | */ | ||
| 131 | private byte[] rowIndexShifts = null; | ||
| 132 | |||
| 133 | //========================================================================= | ||
| 134 | // deserialization | ||
| 135 | //========================================================================= | ||
| 136 | |||
| 137 | public BreakDictionary(String dictionaryName) | ||
| 138 | throws IOException, MissingResourceException { | ||
| 139 | |||
| 140 | readDictionaryFile(dictionaryName); | ||
| 141 | } | ||
| 142 | |||
| 143 | private void readDictionaryFile(final String dictionaryName) | ||
| 144 | throws IOException, MissingResourceException { | ||
| 145 | |||
| 146 | BufferedInputStream in; | ||
| 147 | try { | ||
| 148 | in = (BufferedInputStream)AccessController.doPrivileged( | ||
| 149 | new PrivilegedExceptionAction() { | ||
| 150 | public Object run() throws Exception { | ||
| 151 | return new BufferedInputStream(getClass().getResourceAsStream("/sun/text/resources/" + dictionaryName)); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | ); | ||
| 155 | } | ||
| 156 | catch (PrivilegedActionException e) { | ||
| 157 | throw new InternalError(e.toString()); | ||
| 158 | } | ||
| 159 | |||
| 160 | byte[] buf = new byte[8]; | ||
| 161 | if (in.read(buf) != 8) { | ||
| 162 | throw new MissingResourceException("Wrong data length", | ||
| 163 | dictionaryName, ""); | ||
| 164 | } | ||
| 165 | |||
| 166 | // check vesion | ||
| 167 | int version = BreakIterator.getInt(buf, 0); | ||
| 168 | if (version != supportedVersion) { | ||
| 169 | throw new MissingResourceException("Dictionary version(" + version + ") is unsupported", | ||
| 170 | dictionaryName, ""); | ||
| 171 | } | ||
| 172 | |||
| 173 | // get data size | ||
| 174 | int len = BreakIterator.getInt(buf, 4); | ||
| 175 | buf = new byte[len]; | ||
| 176 | if (in.read(buf) != len) { | ||
| 177 | throw new MissingResourceException("Wrong data length", | ||
| 178 | dictionaryName, ""); | ||
| 179 | } | ||
| 180 | |||
| 181 | // close the stream | ||
| 182 | in.close(); | ||
| 183 | |||
| 184 | int l; | ||
| 185 | int offset = 0; | ||
| 186 | |||
| 187 | // read in the column map for BMP characteres (this is serialized in | ||
| 188 | // its internal form: an index array followed by a data array) | ||
| 189 | l = BreakIterator.getInt(buf, offset); | ||
| 190 | offset += 4; | ||
| 191 | short[] temp = new short[l]; | ||
| 192 | for (int i = 0; i < l; i++, offset+=2) { | ||
| 193 | temp[i] = BreakIterator.getShort(buf, offset); | ||
| 194 | } | ||
| 195 | l = BreakIterator.getInt(buf, offset); | ||
| 196 | offset += 4; | ||
| 197 | byte[] temp2 = new byte[l]; | ||
| 198 | for (int i = 0; i < l; i++, offset++) { | ||
| 199 | temp2[i] = buf[offset]; | ||
| 200 | } | ||
| 201 | columnMap = new CompactByteArray(temp, temp2); | ||
| 202 | |||
| 203 | // read in numCols and numColGroups | ||
| 204 | numCols = BreakIterator.getInt(buf, offset); | ||
| 205 | offset += 4; | ||
| 206 | numColGroups = BreakIterator.getInt(buf, offset); | ||
| 207 | offset += 4; | ||
| 208 | |||
| 209 | // read in the row-number index | ||
| 210 | l = BreakIterator.getInt(buf, offset); | ||
| 211 | offset += 4; | ||
| 212 | rowIndex = new short[l]; | ||
| 213 | for (int i = 0; i < l; i++, offset+=2) { | ||
| 214 | rowIndex[i] = BreakIterator.getShort(buf, offset); | ||
| 215 | } | ||
| 216 | |||
| 217 | // load in the populated-cells bitmap: index first, then bitmap list | ||
| 218 | l = BreakIterator.getInt(buf, offset); | ||
| 219 | offset += 4; | ||
| 220 | rowIndexFlagsIndex = new short[l]; | ||
| 221 | for (int i = 0; i < l; i++, offset+=2) { | ||
| 222 | rowIndexFlagsIndex[i] = BreakIterator.getShort(buf, offset); | ||
| 223 | } | ||
| 224 | l = BreakIterator.getInt(buf, offset); | ||
| 225 | offset += 4; | ||
| 226 | rowIndexFlags = new int[l]; | ||
| 227 | for (int i = 0; i < l; i++, offset+=4) { | ||
| 228 | rowIndexFlags[i] = BreakIterator.getInt(buf, offset); | ||
| 229 | } | ||
| 230 | |||
| 231 | // load in the row-shift index | ||
| 232 | l = BreakIterator.getInt(buf, offset); | ||
| 233 | offset += 4; | ||
| 234 | rowIndexShifts = new byte[l]; | ||
| 235 | for (int i = 0; i < l; i++, offset++) { | ||
| 236 | rowIndexShifts[i] = buf[offset]; | ||
| 237 | } | ||
| 238 | |||
| 239 | // load in the actual state table | ||
| 240 | l = BreakIterator.getInt(buf, offset); | ||
| 241 | offset += 4; | ||
| 242 | table = new short[l]; | ||
| 243 | for (int i = 0; i < l; i++, offset+=2) { | ||
| 244 | table[i] = BreakIterator.getShort(buf, offset); | ||
| 245 | } | ||
| 246 | |||
| 247 | // finally, prepare the column map for supplementary characters | ||
| 248 | l = BreakIterator.getInt(buf, offset); | ||
| 249 | offset += 4; | ||
| 250 | int[] temp3 = new int[l]; | ||
| 251 | for (int i = 0; i < l; i++, offset+=4) { | ||
| 252 | temp3[i] = BreakIterator.getInt(buf, offset); | ||
| 253 | } | ||
| 254 | supplementaryCharColumnMap = new SupplementaryCharacterData(temp3); | ||
| 255 | } | ||
| 256 | |||
| 257 | //========================================================================= | ||
| 258 | // access to the words | ||
| 259 | //========================================================================= | ||
| 260 | |||
| 261 | /** | ||
| 262 | * Uses the column map to map the character to a column number, then | ||
| 263 | * passes the row and column number to getNextState() | ||
| 264 | * @param row The current state | ||
| 265 | * @param ch The character whose column we're interested in | ||
| 266 | * @return The new state to transition to | ||
| 267 | */ | ||
| 268 | public final short getNextStateFromCharacter(int row, int ch) { | ||
| 269 | int col; | ||
| 270 | if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { | ||
| 271 | col = columnMap.elementAt((char)ch); | ||
| 272 | } else { | ||
| 273 | col = supplementaryCharColumnMap.getValue(ch); | ||
| 274 | } | ||
| 275 | return getNextState(row, col); | ||
| 276 | } | ||
| 277 | |||
| 278 | /** | ||
| 279 | * Returns the value in the cell with the specified (logical) row and | ||
| 280 | * column numbers. In DictionaryBasedBreakIterator, the row number is | ||
| 281 | * a state number, the column number is an input, and the return value | ||
| 282 | * is the row number of the new state to transition to. (0 is the | ||
| 283 | * "error" state, and -1 is the "end of word" state in a dictionary) | ||
| 284 | * @param row The row number of the current state | ||
| 285 | * @param col The column number of the input character (0 means "not a | ||
| 286 | * dictionary character") | ||
| 287 | * @return The row number of the new state to transition to | ||
| 288 | */ | ||
| 289 | public final short getNextState(int row, int col) { | ||
| 290 | if (cellIsPopulated(row, col)) { | ||
| 291 | // we map from logical to physical row number by looking up the | ||
| 292 | // mapping in rowIndex; we map from logical column number to | ||
| 293 | // physical column number by looking up a shift value for this | ||
| 294 | // logical row and offsetting the logical column number by | ||
| 295 | // the shift amount. Then we can use internalAt() to actually | ||
| 296 | // get the value out of the table. | ||
| 297 | return internalAt(rowIndex[row], col + rowIndexShifts[row]); | ||
| 298 | } | ||
| 299 | else { | ||
| 300 | return 0; | ||
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | /** | ||
| 305 | * Given (logical) row and column numbers, returns true if the | ||
| 306 | * cell in that position is populated | ||
| 307 | */ | ||
| 308 | private final boolean cellIsPopulated(int row, int col) { | ||
| 309 | // look up the entry in the bitmap index for the specified row. | ||
| 310 | // If it's a negative number, it's the column number of the only | ||
| 311 | // populated cell in the row | ||
| 312 | if (rowIndexFlagsIndex[row] < 0) { | ||
| 313 | return col == -rowIndexFlagsIndex[row]; | ||
| 314 | } | ||
| 315 | |||
| 316 | // if it's a positive number, it's the offset of an entry in the bitmap | ||
| 317 | // list. If the table is more than 32 columns wide, the bitmap is stored | ||
| 318 | // successive entries in the bitmap list, so we have to divide the column | ||
| 319 | // number by 32 and offset the number we got out of the index by the result. | ||
| 320 | // Once we have the appropriate piece of the bitmap, test the appropriate | ||
| 321 | // bit and return the result. | ||
| 322 | else { | ||
| 323 | int flags = rowIndexFlags[rowIndexFlagsIndex[row] + (col >> 5)]; | ||
| 324 | return (flags & (1 << (col & 0x1f))) != 0; | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | /** | ||
| 329 | * Implementation of getNextState() when we know the specified cell is | ||
| 330 | * populated. | ||
| 331 | * @param row The PHYSICAL row number of the cell | ||
| 332 | * @param col The PHYSICAL column number of the cell | ||
| 333 | * @return The value stored in the cell | ||
| 334 | */ | ||
| 335 | private final short internalAt(int row, int col) { | ||
| 336 | // the table is a one-dimensional array, so this just does the math necessary | ||
| 337 | // to treat it as a two-dimensional array (we don't just use a two-dimensional | ||
| 338 | // array because two-dimensional arrays are inefficient in Java) | ||
| 339 | return table[row * numCols + col]; | ||
| 340 | } | ||
| 341 | } | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,715 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation | ||
| 31 | * is copyrighted and owned by Taligent, Inc., a wholly-owned | ||
| 32 | * subsidiary of IBM. These materials are provided under terms | ||
| 33 | * of a License Agreement between Taligent and Sun. This technology | ||
| 34 | * is protected by multiple US and International patents. | ||
| 35 | * | ||
| 36 | * This notice and attribution to Taligent may not be removed. | ||
| 37 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 38 | * | ||
| 39 | */ | ||
| 40 | |||
| 41 | package java.text; | ||
| 42 | |||
| 43 | import java.lang.ref.SoftReference; | ||
| 44 | import java.net.URL; | ||
| 45 | import java.io.InputStream; | ||
| 46 | import java.io.IOException; | ||
| 47 | import java.security.AccessController; | ||
| 48 | import java.security.PrivilegedAction; | ||
| 49 | import java.text.CharacterIterator; | ||
| 50 | import java.text.StringCharacterIterator; | ||
| 51 | import java.text.spi.BreakIteratorProvider; | ||
| 52 | import java.util.Locale; | ||
| 53 | import java.util.MissingResourceException; | ||
| 54 | import java.util.ResourceBundle; | ||
| 55 | import java.util.spi.LocaleServiceProvider; | ||
| 56 | import sun.util.LocaleServiceProviderPool; | ||
| 57 | import sun.util.resources.LocaleData; | ||
| 58 | |||
| 59 | |||
| 60 | /** | ||
| 61 | * The <code>BreakIterator</code> class implements methods for finding | ||
| 62 | * the location of boundaries in text. Instances of <code>BreakIterator</code> | ||
| 63 | * maintain a current position and scan over text | ||
| 64 | * returning the index of characters where boundaries occur. | ||
| 65 | * Internally, <code>BreakIterator</code> scans text using a | ||
| 66 | * <code>CharacterIterator</code>, and is thus able to scan text held | ||
| 67 | * by any object implementing that protocol. A <code>StringCharacterIterator</code> | ||
| 68 | * is used to scan <code>String</code> objects passed to <code>setText</code>. | ||
| 69 | * | ||
| 70 | * <p> | ||
| 71 | * You use the factory methods provided by this class to create | ||
| 72 | * instances of various types of break iterators. In particular, | ||
| 73 | * use <code>getWordInstance</code>, <code>getLineInstance</code>, | ||
| 74 | * <code>getSentenceInstance</code>, and <code>getCharacterInstance</code> | ||
| 75 | * to create <code>BreakIterator</code>s that perform | ||
| 76 | * word, line, sentence, and character boundary analysis respectively. | ||
| 77 | * A single <code>BreakIterator</code> can work only on one unit | ||
| 78 | * (word, line, sentence, and so on). You must use a different iterator | ||
| 79 | * for each unit boundary analysis you wish to perform. | ||
| 80 | * | ||
| 81 | * <p><a name="line"></a> | ||
| 82 | * Line boundary analysis determines where a text string can be | ||
| 83 | * broken when line-wrapping. The mechanism correctly handles | ||
| 84 | * punctuation and hyphenated words. Actual line breaking needs | ||
| 85 | * to also consider the available line width and is handled by | ||
| 86 | * higher-level software. | ||
| 87 | * | ||
| 88 | * <p><a name="sentence"></a> | ||
| 89 | * Sentence boundary analysis allows selection with correct interpretation | ||
| 90 | * of periods within numbers and abbreviations, and trailing punctuation | ||
| 91 | * marks such as quotation marks and parentheses. | ||
| 92 | * | ||
| 93 | * <p><a name="word"></a> | ||
| 94 | * Word boundary analysis is used by search and replace functions, as | ||
| 95 | * well as within text editing applications that allow the user to | ||
| 96 | * select words with a double click. Word selection provides correct | ||
| 97 | * interpretation of punctuation marks within and following | ||
| 98 | * words. Characters that are not part of a word, such as symbols | ||
| 99 | * or punctuation marks, have word-breaks on both sides. | ||
| 100 | * | ||
| 101 | * <p><a name="character"></a> | ||
| 102 | * Character boundary analysis allows users to interact with characters | ||
| 103 | * as they expect to, for example, when moving the cursor through a text | ||
| 104 | * string. Character boundary analysis provides correct navigation | ||
| 105 | * through character strings, regardless of how the character is stored. | ||
| 106 | * The boundaries returned may be those of supplementary characters, | ||
| 107 | * combining character sequences, or ligature clusters. | ||
| 108 | * For example, an accented character might be stored as a base character | ||
| 109 | * and a diacritical mark. What users consider to be a character can | ||
| 110 | * differ between languages. | ||
| 111 | * | ||
| 112 | * <p> | ||
| 113 | * The <code>BreakIterator</code> instances returned by the factory methods | ||
| 114 | * of this class are intended for use with natural languages only, not for | ||
| 115 | * programming language text. It is however possible to define subclasses | ||
| 116 | * that tokenize a programming language. | ||
| 117 | * | ||
| 118 | * <P> | ||
| 119 | * <strong>Examples</strong>:<P> | ||
| 120 | * Creating and using text boundaries: | ||
| 121 | * <blockquote> | ||
| 122 | * <pre> | ||
| 123 | * public static void main(String args[]) { | ||
| 124 | * if (args.length == 1) { | ||
| 125 | * String stringToExamine = args[0]; | ||
| 126 | * //print each word in order | ||
| 127 | * BreakIterator boundary = BreakIterator.getWordInstance(); | ||
| 128 | * boundary.setText(stringToExamine); | ||
| 129 | * printEachForward(boundary, stringToExamine); | ||
| 130 | * //print each sentence in reverse order | ||
| 131 | * boundary = BreakIterator.getSentenceInstance(Locale.US); | ||
| 132 | * boundary.setText(stringToExamine); | ||
| 133 | * printEachBackward(boundary, stringToExamine); | ||
| 134 | * printFirst(boundary, stringToExamine); | ||
| 135 | * printLast(boundary, stringToExamine); | ||
| 136 | * } | ||
| 137 | * } | ||
| 138 | * </pre> | ||
| 139 | * </blockquote> | ||
| 140 | * | ||
| 141 | * Print each element in order: | ||
| 142 | * <blockquote> | ||
| 143 | * <pre> | ||
| 144 | * public static void printEachForward(BreakIterator boundary, String source) { | ||
| 145 | * int start = boundary.first(); | ||
| 146 | * for (int end = boundary.next(); | ||
| 147 | * end != BreakIterator.DONE; | ||
| 148 | * start = end, end = boundary.next()) { | ||
| 149 | * System.out.println(source.substring(start,end)); | ||
| 150 | * } | ||
| 151 | * } | ||
| 152 | * </pre> | ||
| 153 | * </blockquote> | ||
| 154 | * | ||
| 155 | * Print each element in reverse order: | ||
| 156 | * <blockquote> | ||
| 157 | * <pre> | ||
| 158 | * public static void printEachBackward(BreakIterator boundary, String source) { | ||
| 159 | * int end = boundary.last(); | ||
| 160 | * for (int start = boundary.previous(); | ||
| 161 | * start != BreakIterator.DONE; | ||
| 162 | * end = start, start = boundary.previous()) { | ||
| 163 | * System.out.println(source.substring(start,end)); | ||
| 164 | * } | ||
| 165 | * } | ||
| 166 | * </pre> | ||
| 167 | * </blockquote> | ||
| 168 | * | ||
| 169 | * Print first element: | ||
| 170 | * <blockquote> | ||
| 171 | * <pre> | ||
| 172 | * public static void printFirst(BreakIterator boundary, String source) { | ||
| 173 | * int start = boundary.first(); | ||
| 174 | * int end = boundary.next(); | ||
| 175 | * System.out.println(source.substring(start,end)); | ||
| 176 | * } | ||
| 177 | * </pre> | ||
| 178 | * </blockquote> | ||
| 179 | * | ||
| 180 | * Print last element: | ||
| 181 | * <blockquote> | ||
| 182 | * <pre> | ||
| 183 | * public static void printLast(BreakIterator boundary, String source) { | ||
| 184 | * int end = boundary.last(); | ||
| 185 | * int start = boundary.previous(); | ||
| 186 | * System.out.println(source.substring(start,end)); | ||
| 187 | * } | ||
| 188 | * </pre> | ||
| 189 | * </blockquote> | ||
| 190 | * | ||
| 191 | * Print the element at a specified position: | ||
| 192 | * <blockquote> | ||
| 193 | * <pre> | ||
| 194 | * public static void printAt(BreakIterator boundary, int pos, String source) { | ||
| 195 | * int end = boundary.following(pos); | ||
| 196 | * int start = boundary.previous(); | ||
| 197 | * System.out.println(source.substring(start,end)); | ||
| 198 | * } | ||
| 199 | * </pre> | ||
| 200 | * </blockquote> | ||
| 201 | * | ||
| 202 | * Find the next word: | ||
| 203 | * <blockquote> | ||
| 204 | * <pre> | ||
| 205 | * public static int nextWordStartAfter(int pos, String text) { | ||
| 206 | * BreakIterator wb = BreakIterator.getWordInstance(); | ||
| 207 | * wb.setText(text); | ||
| 208 | * int last = wb.following(pos); | ||
| 209 | * int current = wb.next(); | ||
| 210 | * while (current != BreakIterator.DONE) { | ||
| 211 | * for (int p = last; p < current; p++) { | ||
| 212 | * if (Character.isLetter(text.codePointAt(p))) | ||
| 213 | * return last; | ||
| 214 | * } | ||
| 215 | * last = current; | ||
| 216 | * current = wb.next(); | ||
| 217 | * } | ||
| 218 | * return BreakIterator.DONE; | ||
| 219 | * } | ||
| 220 | * </pre> | ||
| 221 | * (The iterator returned by BreakIterator.getWordInstance() is unique in that | ||
| 222 | * the break positions it returns don't represent both the start and end of the | ||
| 223 | * thing being iterated over. That is, a sentence-break iterator returns breaks | ||
| 224 | * that each represent the end of one sentence and the beginning of the next. | ||
| 225 | * With the word-break iterator, the characters between two boundaries might be a | ||
| 226 | * word, or they might be the punctuation or whitespace between two words. The | ||
| 227 | * above code uses a simple heuristic to determine which boundary is the beginning | ||
| 228 | * of a word: If the characters between this boundary and the next boundary | ||
| 229 | * include at least one letter (this can be an alphabetical letter, a CJK ideograph, | ||
| 230 | * a Hangul syllable, a Kana character, etc.), then the text between this boundary | ||
| 231 | * and the next is a word; otherwise, it's the material between words.) | ||
| 232 | * </blockquote> | ||
| 233 | * | ||
| 234 | * @see CharacterIterator | ||
| 235 | * | ||
| 236 | */ | ||
| 237 | |||
| 238 | public abstract class BreakIterator implements Cloneable | ||
| 239 | { | ||
| 240 | /** | ||
| 241 | * Constructor. BreakIterator is stateless and has no default behavior. | ||
| 242 | */ | ||
| 243 | protected BreakIterator() | ||
| 244 | { | ||
| 245 | } | ||
| 246 | |||
| 247 | /** | ||
| 248 | * Create a copy of this iterator | ||
| 249 | * @return A copy of this | ||
| 250 | */ | ||
| 251 | public Object clone() | ||
| 252 | { | ||
| 253 | try { | ||
| 254 | return super.clone(); | ||
| 255 | } | ||
| 256 | catch (CloneNotSupportedException e) { | ||
| 257 | throw new InternalError(); | ||
| 258 | } | ||
| 259 | } | ||
| 260 | |||
| 261 | /** | ||
| 262 | * DONE is returned by previous(), next(), next(int), preceding(int) | ||
| 263 | * and following(int) when either the first or last text boundary has been | ||
| 264 | * reached. | ||
| 265 | */ | ||
| 266 | public static final int DONE = -1; | ||
| 267 | |||
| 268 | /** | ||
| 269 | * Returns the first boundary. The iterator's current position is set | ||
| 270 | * to the first text boundary. | ||
| 271 | * @return The character index of the first text boundary. | ||
| 272 | */ | ||
| 273 | public abstract int first(); | ||
| 274 | |||
| 275 | /** | ||
| 276 | * Returns the last boundary. The iterator's current position is set | ||
| 277 | * to the last text boundary. | ||
| 278 | * @return The character index of the last text boundary. | ||
| 279 | */ | ||
| 280 | public abstract int last(); | ||
| 281 | |||
| 282 | /** | ||
| 283 | * Returns the nth boundary from the current boundary. If either | ||
| 284 | * the first or last text boundary has been reached, it returns | ||
| 285 | * <code>BreakIterator.DONE</code> and the current position is set to either | ||
| 286 | * the first or last text boundary depending on which one is reached. Otherwise, | ||
| 287 | * the iterator's current position is set to the new boundary. | ||
| 288 | * For example, if the iterator's current position is the mth text boundary | ||
| 289 | * and three more boundaries exist from the current boundary to the last text | ||
| 290 | * boundary, the next(2) call will return m + 2. The new text position is set | ||
| 291 | * to the (m + 2)th text boundary. A next(4) call would return | ||
| 292 | * <code>BreakIterator.DONE</code> and the last text boundary would become the | ||
| 293 | * new text position. | ||
| 294 | * @param n which boundary to return. A value of 0 | ||
| 295 | * does nothing. Negative values move to previous boundaries | ||
| 296 | * and positive values move to later boundaries. | ||
| 297 | * @return The character index of the nth boundary from the current position | ||
| 298 | * or <code>BreakIterator.DONE</code> if either first or last text boundary | ||
| 299 | * has been reached. | ||
| 300 | */ | ||
| 301 | public abstract int next(int n); | ||
| 302 | |||
| 303 | /** | ||
| 304 | * Returns the boundary following the current boundary. If the current boundary | ||
| 305 | * is the last text boundary, it returns <code>BreakIterator.DONE</code> and | ||
| 306 | * the iterator's current position is unchanged. Otherwise, the iterator's | ||
| 307 | * current position is set to the boundary following the current boundary. | ||
| 308 | * @return The character index of the next text boundary or | ||
| 309 | * <code>BreakIterator.DONE</code> if the current boundary is the last text | ||
| 310 | * boundary. | ||
| 311 | * Equivalent to next(1). | ||
| 312 | * @see #next(int) | ||
| 313 | */ | ||
| 314 | public abstract int next(); | ||
| 315 | |||
| 316 | /** | ||
| 317 | * Returns the boundary preceding the current boundary. If the current boundary | ||
| 318 | * is the first text boundary, it returns <code>BreakIterator.DONE</code> and | ||
| 319 | * the iterator's current position is unchanged. Otherwise, the iterator's | ||
| 320 | * current position is set to the boundary preceding the current boundary. | ||
| 321 | * @return The character index of the previous text boundary or | ||
| 322 | * <code>BreakIterator.DONE</code> if the current boundary is the first text | ||
| 323 | * boundary. | ||
| 324 | */ | ||
| 325 | public abstract int previous(); | ||
| 326 | |||
| 327 | /** | ||
| 328 | * Returns the first boundary following the specified character offset. If the | ||
| 329 | * specified offset equals to the last text boundary, it returns | ||
| 330 | * <code>BreakIterator.DONE</code> and the iterator's current position is unchanged. | ||
| 331 | * Otherwise, the iterator's current position is set to the returned boundary. | ||
| 332 | * The value returned is always greater than the offset or the value | ||
| 333 | * <code>BreakIterator.DONE</code>. | ||
| 334 | * @param offset the character offset to begin scanning. | ||
| 335 | * @return The first boundary after the specified offset or | ||
| 336 | * <code>BreakIterator.DONE</code> if the last text boundary is passed in | ||
| 337 | * as the offset. | ||
| 338 | * @exception IllegalArgumentException if the specified offset is less than | ||
| 339 | * the first text boundary or greater than the last text boundary. | ||
| 340 | */ | ||
| 341 | public abstract int following(int offset); | ||
| 342 | |||
| 343 | /** | ||
| 344 | * Returns the last boundary preceding the specified character offset. If the | ||
| 345 | * specified offset equals to the first text boundary, it returns | ||
| 346 | * <code>BreakIterator.DONE</code> and the iterator's current position is unchanged. | ||
| 347 | * Otherwise, the iterator's current position is set to the returned boundary. | ||
| 348 | * The value returned is always less than the offset or the value | ||
| 349 | * <code>BreakIterator.DONE</code>. | ||
| 350 | * @param offset the characater offset to begin scanning. | ||
| 351 | * @return The last boundary before the specified offset or | ||
| 352 | * <code>BreakIterator.DONE</code> if the first text boundary is passed in | ||
| 353 | * as the offset. | ||
| 354 | * @exception IllegalArgumentException if the specified offset is less than | ||
| 355 | * the first text boundary or greater than the last text boundary. | ||
| 356 | * @since 1.2 | ||
| 357 | */ | ||
| 358 | public int preceding(int offset) { | ||
| 359 | // NOTE: This implementation is here solely because we can't add new | ||
| 360 | // abstract methods to an existing class. There is almost ALWAYS a | ||
| 361 | // better, faster way to do this. | ||
| 362 | int pos = following(offset); | ||
| 363 | while (pos >= offset && pos != DONE) | ||
| 364 | pos = previous(); | ||
| 365 | return pos; | ||
| 366 | } | ||
| 367 | |||
| 368 | /** | ||
| 369 | * Returns true if the specified character offset is a text boundary. | ||
| 370 | * @param offset the character offset to check. | ||
| 371 | * @return <code>true</code> if "offset" is a boundary position, | ||
| 372 | * <code>false</code> otherwise. | ||
| 373 | * @exception IllegalArgumentException if the specified offset is less than | ||
| 374 | * the first text boundary or greater than the last text boundary. | ||
| 375 | * @since 1.2 | ||
| 376 | */ | ||
| 377 | public boolean isBoundary(int offset) { | ||
| 378 | // NOTE: This implementation probably is wrong for most situations | ||
| 379 | // because it fails to take into account the possibility that a | ||
| 380 | // CharacterIterator passed to setText() may not have a begin offset | ||
| 381 | // of 0. But since the abstract BreakIterator doesn't have that | ||
| 382 | // knowledge, it assumes the begin offset is 0. If you subclass | ||
| 383 | // BreakIterator, copy the SimpleTextBoundary implementation of this | ||
| 384 | // function into your subclass. [This should have been abstract at | ||
| 385 | // this level, but it's too late to fix that now.] | ||
| 386 | if (offset == 0) { | ||
| 387 | return true; | ||
| 388 | } | ||
| 389 | int boundary = following(offset - 1); | ||
| 390 | if (boundary == DONE) { | ||
| 391 | throw new IllegalArgumentException(); | ||
| 392 | } | ||
| 393 | return boundary == offset; | ||
| 394 | } | ||
| 395 | |||
| 396 | /** | ||
| 397 | * Returns character index of the text boundary that was most | ||
| 398 | * recently returned by next(), next(int), previous(), first(), last(), | ||
| 399 | * following(int) or preceding(int). If any of these methods returns | ||
| 400 | * <code>BreakIterator.DONE</code> because either first or last text boundary | ||
| 401 | * has been reached, it returns the first or last text boundary depending on | ||
| 402 | * which one is reached. | ||
| 403 | * @return The text boundary returned from the above methods, first or last | ||
| 404 | * text boundary. | ||
| 405 | * @see #next() | ||
| 406 | * @see #next(int) | ||
| 407 | * @see #previous() | ||
| 408 | * @see #first() | ||
| 409 | * @see #last() | ||
| 410 | * @see #following(int) | ||
| 411 | * @see #preceding(int) | ||
| 412 | */ | ||
| 413 | public abstract int current(); | ||
| 414 | |||
| 415 | /** | ||
| 416 | * Get the text being scanned | ||
| 417 | * @return the text being scanned | ||
| 418 | */ | ||
| 419 | public abstract CharacterIterator getText(); | ||
| 420 | |||
| 421 | /** | ||
| 422 | * Set a new text string to be scanned. The current scan | ||
| 423 | * position is reset to first(). | ||
| 424 | * @param newText new text to scan. | ||
| 425 | */ | ||
| 426 | public void setText(String newText) | ||
| 427 | { | ||
| 428 | setText(new StringCharacterIterator(newText)); | ||
| 429 | } | ||
| 430 | |||
| 431 | /** | ||
| 432 | * Set a new text for scanning. The current scan | ||
| 433 | * position is reset to first(). | ||
| 434 | * @param newText new text to scan. | ||
| 435 | */ | ||
| 436 | public abstract void setText(CharacterIterator newText); | ||
| 437 | |||
| 438 | private static final int CHARACTER_INDEX = 0; | ||
| 439 | private static final int WORD_INDEX = 1; | ||
| 440 | private static final int LINE_INDEX = 2; | ||
| 441 | private static final int SENTENCE_INDEX = 3; | ||
| 442 | private static final SoftReference[] iterCache = new SoftReference[4]; | ||
| 443 | |||
| 444 | /** | ||
| 445 | * Returns a new <code>BreakIterator</code> instance | ||
| 446 | * for <a href="#word">word breaks</a> | ||
| 447 | * for the {@linkplain Locale#getDefault() default locale}. | ||
| 448 | * @return A break iterator for word breaks | ||
| 449 | */ | ||
| 450 | public static BreakIterator getWordInstance() | ||
| 451 | { | ||
| 452 | return getWordInstance(Locale.getDefault()); | ||
| 453 | } | ||
| 454 | |||
| 455 | /** | ||
| 456 | * Returns a new <code>BreakIterator</code> instance | ||
| 457 | * for <a href="#word">word breaks</a> | ||
| 458 | * for the given locale. | ||
| 459 | * @param locale the desired locale | ||
| 460 | * @return A break iterator for word breaks | ||
| 461 | * @exception NullPointerException if <code>locale</code> is null | ||
| 462 | */ | ||
| 463 | public static BreakIterator getWordInstance(Locale locale) | ||
| 464 | { | ||
| 465 | return getBreakInstance(locale, | ||
| 466 | WORD_INDEX, | ||
| 467 | "WordData", | ||
| 468 | "WordDictionary"); | ||
| 469 | } | ||
| 470 | |||
| 471 | /** | ||
| 472 | * Returns a new <code>BreakIterator</code> instance | ||
| 473 | * for <a href="#line">line breaks</a> | ||
| 474 | * for the {@linkplain Locale#getDefault() default locale}. | ||
| 475 | * @return A break iterator for line breaks | ||
| 476 | */ | ||
| 477 | public static BreakIterator getLineInstance() | ||
| 478 | { | ||
| 479 | return getLineInstance(Locale.getDefault()); | ||
| 480 | } | ||
| 481 | |||
| 482 | /** | ||
| 483 | * Returns a new <code>BreakIterator</code> instance | ||
| 484 | * for <a href="#line">line breaks</a> | ||
| 485 | * for the given locale. | ||
| 486 | * @param locale the desired locale | ||
| 487 | * @return A break iterator for line breaks | ||
| 488 | * @exception NullPointerException if <code>locale</code> is null | ||
| 489 | */ | ||
| 490 | public static BreakIterator getLineInstance(Locale locale) | ||
| 491 | { | ||
| 492 | return getBreakInstance(locale, | ||
| 493 | LINE_INDEX, | ||
| 494 | "LineData", | ||
| 495 | "LineDictionary"); | ||
| 496 | } | ||
| 497 | |||
| 498 | /** | ||
| 499 | * Returns a new <code>BreakIterator</code> instance | ||
| 500 | * for <a href="#character">character breaks</a> | ||
| 501 | * for the {@linkplain Locale#getDefault() default locale}. | ||
| 502 | * @return A break iterator for character breaks | ||
| 503 | */ | ||
| 504 | public static BreakIterator getCharacterInstance() | ||
| 505 | { | ||
| 506 | return getCharacterInstance(Locale.getDefault()); | ||
| 507 | } | ||
| 508 | |||
| 509 | /** | ||
| 510 | * Returns a new <code>BreakIterator</code> instance | ||
| 511 | * for <a href="#character">character breaks</a> | ||
| 512 | * for the given locale. | ||
| 513 | * @param locale the desired locale | ||
| 514 | * @return A break iterator for character breaks | ||
| 515 | * @exception NullPointerException if <code>locale</code> is null | ||
| 516 | */ | ||
| 517 | public static BreakIterator getCharacterInstance(Locale locale) | ||
| 518 | { | ||
| 519 | return getBreakInstance(locale, | ||
| 520 | CHARACTER_INDEX, | ||
| 521 | "CharacterData", | ||
| 522 | "CharacterDictionary"); | ||
| 523 | } | ||
| 524 | |||
| 525 | /** | ||
| 526 | * Returns a new <code>BreakIterator</code> instance | ||
| 527 | * for <a href="#sentence">sentence breaks</a> | ||
| 528 | * for the {@linkplain Locale#getDefault() default locale}. | ||
| 529 | * @return A break iterator for sentence breaks | ||
| 530 | */ | ||
| 531 | public static BreakIterator getSentenceInstance() | ||
| 532 | { | ||
| 533 | return getSentenceInstance(Locale.getDefault()); | ||
| 534 | } | ||
| 535 | |||
| 536 | /** | ||
| 537 | * Returns a new <code>BreakIterator</code> instance | ||
| 538 | * for <a href="#sentence">sentence breaks</a> | ||
| 539 | * for the given locale. | ||
| 540 | * @param locale the desired locale | ||
| 541 | * @return A break iterator for sentence breaks | ||
| 542 | * @exception NullPointerException if <code>locale</code> is null | ||
| 543 | */ | ||
| 544 | public static BreakIterator getSentenceInstance(Locale locale) | ||
| 545 | { | ||
| 546 | return getBreakInstance(locale, | ||
| 547 | SENTENCE_INDEX, | ||
| 548 | "SentenceData", | ||
| 549 | "SentenceDictionary"); | ||
| 550 | } | ||
| 551 | |||
| 552 | private static BreakIterator getBreakInstance(Locale locale, | ||
| 553 | int type, | ||
| 554 | String dataName, | ||
| 555 | String dictionaryName) { | ||
| 556 | if (iterCache[type] != null) { | ||
| 557 | BreakIteratorCache cache = (BreakIteratorCache) iterCache[type].get(); | ||
| 558 | if (cache != null) { | ||
| 559 | if (cache.getLocale().equals(locale)) { | ||
| 560 | return cache.createBreakInstance(); | ||
| 561 | } | ||
| 562 | } | ||
| 563 | } | ||
| 564 | |||
| 565 | BreakIterator result = createBreakInstance(locale, | ||
| 566 | type, | ||
| 567 | dataName, | ||
| 568 | dictionaryName); | ||
| 569 | BreakIteratorCache cache = new BreakIteratorCache(locale, result); | ||
| 570 | iterCache[type] = new SoftReference(cache); | ||
| 571 | return result; | ||
| 572 | } | ||
| 573 | |||
| 574 | private static ResourceBundle getBundle(final String baseName, final Locale locale) { | ||
| 575 | return (ResourceBundle) AccessController.doPrivileged(new PrivilegedAction() { | ||
| 576 | public Object run() { | ||
| 577 | return ResourceBundle.getBundle(baseName, locale); | ||
| 578 | } | ||
| 579 | }); | ||
| 580 | } | ||
| 581 | |||
| 582 | private static BreakIterator createBreakInstance(Locale locale, | ||
| 583 | int type, | ||
| 584 | String dataName, | ||
| 585 | String dictionaryName) { | ||
| 586 | |||
| 587 | // Check whether a provider can provide an implementation that's closer | ||
| 588 | // to the requested locale than what the Java runtime itself can provide. | ||
| 589 | LocaleServiceProviderPool pool = | ||
| 590 | LocaleServiceProviderPool.getPool(BreakIteratorProvider.class); | ||
| 591 | if (pool.hasProviders()) { | ||
| 592 | BreakIterator providersInstance = pool.getLocalizedObject( | ||
| 593 | BreakIteratorGetter.INSTANCE, | ||
| 594 | locale, type); | ||
| 595 | if (providersInstance != null) { | ||
| 596 | return providersInstance; | ||
| 597 | } | ||
| 598 | } | ||
| 599 | |||
| 600 | ResourceBundle bundle = getBundle( | ||
| 601 | "sun.text.resources.BreakIteratorInfo", locale); | ||
| 602 | String[] classNames = bundle.getStringArray("BreakIteratorClasses"); | ||
| 603 | |||
| 604 | String dataFile = bundle.getString(dataName); | ||
| 605 | |||
| 606 | try { | ||
| 607 | if (classNames[type].equals("RuleBasedBreakIterator")) { | ||
| 608 | return new RuleBasedBreakIterator(dataFile); | ||
| 609 | } | ||
| 610 | else if (classNames[type].equals("DictionaryBasedBreakIterator")) { | ||
| 611 | String dictionaryFile = bundle.getString(dictionaryName); | ||
| 612 | return new DictionaryBasedBreakIterator(dataFile, dictionaryFile); | ||
| 613 | } | ||
| 614 | else { | ||
| 615 | throw new IllegalArgumentException("Invalid break iterator class \"" + | ||
| 616 | classNames[type] + "\""); | ||
| 617 | } | ||
| 618 | } | ||
| 619 | catch (Exception e) { | ||
| 620 | throw new InternalError(e.toString()); | ||
| 621 | } | ||
| 622 | } | ||
| 623 | |||
| 624 | /** | ||
| 625 | * Returns an array of all locales for which the | ||
| 626 | * <code>get*Instance</code> methods of this class can return | ||
| 627 | * localized instances. | ||
| 628 | * The returned array represents the union of locales supported by the Java | ||
| 629 | * runtime and by installed | ||
| 630 | * {@link java.text.spi.BreakIteratorProvider BreakIteratorProvider} implementations. | ||
| 631 | * It must contain at least a <code>Locale</code> | ||
| 632 | * instance equal to {@link java.util.Locale#US Locale.US}. | ||
| 633 | * | ||
| 634 | * @return An array of locales for which localized | ||
| 635 | * <code>BreakIterator</code> instances are available. | ||
| 636 | */ | ||
| 637 | public static synchronized Locale[] getAvailableLocales() | ||
| 638 | { | ||
| 639 | LocaleServiceProviderPool pool = | ||
| 640 | LocaleServiceProviderPool.getPool(BreakIteratorProvider.class); | ||
| 641 | return pool.getAvailableLocales(); | ||
| 642 | } | ||
| 643 | |||
| 644 | private static final class BreakIteratorCache { | ||
| 645 | |||
| 646 | private BreakIterator iter; | ||
| 647 | private Locale locale; | ||
| 648 | |||
| 649 | BreakIteratorCache(Locale locale, BreakIterator iter) { | ||
| 650 | this.locale = locale; | ||
| 651 | this.iter = (BreakIterator) iter.clone(); | ||
| 652 | } | ||
| 653 | |||
| 654 | Locale getLocale() { | ||
| 655 | return locale; | ||
| 656 | } | ||
| 657 | |||
| 658 | BreakIterator createBreakInstance() { | ||
| 659 | return (BreakIterator) iter.clone(); | ||
| 660 | } | ||
| 661 | } | ||
| 662 | |||
| 663 | static long getLong(byte[] buf, int offset) { | ||
| 664 | long num = buf[offset]&0xFF; | ||
| 665 | for (int i = 1; i < 8; i++) { | ||
| 666 | num = num<<8 | (buf[offset+i]&0xFF); | ||
| 667 | } | ||
| 668 | return num; | ||
| 669 | } | ||
| 670 | |||
| 671 | static int getInt(byte[] buf, int offset) { | ||
| 672 | int num = buf[offset]&0xFF; | ||
| 673 | for (int i = 1; i < 4; i++) { | ||
| 674 | num = num<<8 | (buf[offset+i]&0xFF); | ||
| 675 | } | ||
| 676 | return num; | ||
| 677 | } | ||
| 678 | |||
| 679 | static short getShort(byte[] buf, int offset) { | ||
| 680 | short num = (short)(buf[offset]&0xFF); | ||
| 681 | num = (short)(num<<8 | (buf[offset+1]&0xFF)); | ||
| 682 | return num; | ||
| 683 | } | ||
| 684 | |||
| 685 | /** | ||
| 686 | * Obtains a BreakIterator instance from a BreakIteratorProvider | ||
| 687 | * implementation. | ||
| 688 | */ | ||
| 689 | private static class BreakIteratorGetter | ||
| 690 | implements LocaleServiceProviderPool.LocalizedObjectGetter<BreakIteratorProvider, BreakIterator> { | ||
| 691 | private static final BreakIteratorGetter INSTANCE = | ||
| 692 | new BreakIteratorGetter(); | ||
| 693 | |||
| 694 | public BreakIterator getObject(BreakIteratorProvider breakIteratorProvider, | ||
| 695 | Locale locale, | ||
| 696 | String key, | ||
| 697 | Object... params) { | ||
| 698 | assert params.length == 1; | ||
| 699 | |||
| 700 | switch ((Integer)params[0]) { | ||
| 701 | case CHARACTER_INDEX: | ||
| 702 | return breakIteratorProvider.getCharacterInstance(locale); | ||
| 703 | case WORD_INDEX: | ||
| 704 | return breakIteratorProvider.getWordInstance(locale); | ||
| 705 | case LINE_INDEX: | ||
| 706 | return breakIteratorProvider.getLineInstance(locale); | ||
| 707 | case SENTENCE_INDEX: | ||
| 708 | return breakIteratorProvider.getSentenceInstance(locale); | ||
| 709 | default: | ||
| 710 | assert false : "should not happen"; | ||
| 711 | } | ||
| 712 | return null; | ||
| 713 | } | ||
| 714 | } | ||
| 715 | } | ||
Binary file not shown.
| @@ -0,0 +1,170 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | package java.text; | ||
| 27 | |||
| 28 | import java.util.Calendar; | ||
| 29 | import static java.util.GregorianCalendar.*; | ||
| 30 | |||
| 31 | /** | ||
| 32 | * {@code CalendarBuilder} keeps field-value pairs for setting | ||
| 33 | * the calendar fields of the given {@code Calendar}. It has the | ||
| 34 | * {@link Calendar#FIELD_COUNT FIELD_COUNT}-th field for the week year | ||
| 35 | * support. Also {@code ISO_DAY_OF_WEEK} is used to specify | ||
| 36 | * {@code DAY_OF_WEEK} in the ISO day of week numbering. | ||
| 37 | * | ||
| 38 | * <p>{@code CalendarBuilder} retains the semantic of the pseudo | ||
| 39 | * timestamp for fields. {@code CalendarBuilder} uses a single | ||
| 40 | * int array combining fields[] and stamp[] of {@code Calendar}. | ||
| 41 | * | ||
| 42 | * @author Masayoshi Okutsu | ||
| 43 | */ | ||
| 44 | class CalendarBuilder { | ||
| 45 | /* | ||
| 46 | * Pseudo time stamp constants used in java.util.Calendar | ||
| 47 | */ | ||
| 48 | private static final int UNSET = 0; | ||
| 49 | private static final int COMPUTED = 1; | ||
| 50 | private static final int MINIMUM_USER_STAMP = 2; | ||
| 51 | |||
| 52 | private static final int MAX_FIELD = FIELD_COUNT + 1; | ||
| 53 | |||
| 54 | public static final int WEEK_YEAR = FIELD_COUNT; | ||
| 55 | public static final int ISO_DAY_OF_WEEK = 1000; // pseudo field index | ||
| 56 | |||
| 57 | // stamp[] (lower half) and field[] (upper half) combined | ||
| 58 | private final int[] field; | ||
| 59 | private int nextStamp; | ||
| 60 | private int maxFieldIndex; | ||
| 61 | |||
| 62 | CalendarBuilder() { | ||
| 63 | field = new int[MAX_FIELD * 2]; | ||
| 64 | nextStamp = MINIMUM_USER_STAMP; | ||
| 65 | maxFieldIndex = -1; | ||
| 66 | } | ||
| 67 | |||
| 68 | CalendarBuilder set(int index, int value) { | ||
| 69 | if (index == ISO_DAY_OF_WEEK) { | ||
| 70 | index = DAY_OF_WEEK; | ||
| 71 | value = toCalendarDayOfWeek(value); | ||
| 72 | } | ||
| 73 | field[index] = nextStamp++; | ||
| 74 | field[MAX_FIELD + index] = value; | ||
| 75 | if (index > maxFieldIndex && index < FIELD_COUNT) { | ||
| 76 | maxFieldIndex = index; | ||
| 77 | } | ||
| 78 | return this; | ||
| 79 | } | ||
| 80 | |||
| 81 | CalendarBuilder addYear(int value) { | ||
| 82 | field[MAX_FIELD + YEAR] += value; | ||
| 83 | field[MAX_FIELD + WEEK_YEAR] += value; | ||
| 84 | return this; | ||
| 85 | } | ||
| 86 | |||
| 87 | boolean isSet(int index) { | ||
| 88 | if (index == ISO_DAY_OF_WEEK) { | ||
| 89 | index = DAY_OF_WEEK; | ||
| 90 | } | ||
| 91 | return field[index] > UNSET; | ||
| 92 | } | ||
| 93 | |||
| 94 | Calendar establish(Calendar cal) { | ||
| 95 | boolean weekDate = isSet(WEEK_YEAR) | ||
| 96 | && field[WEEK_YEAR] > field[YEAR]; | ||
| 97 | if (weekDate && !cal.isWeekDateSupported()) { | ||
| 98 | // Use YEAR instead | ||
| 99 | if (!isSet(YEAR)) { | ||
| 100 | set(YEAR, field[MAX_FIELD + WEEK_YEAR]); | ||
| 101 | } | ||
| 102 | weekDate = false; | ||
| 103 | } | ||
| 104 | |||
| 105 | cal.clear(); | ||
| 106 | // Set the fields from the min stamp to the max stamp so that | ||
| 107 | // the field resolution works in the Calendar. | ||
| 108 | for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) { | ||
| 109 | for (int index = 0; index <= maxFieldIndex; index++) { | ||
| 110 | if (field[index] == stamp) { | ||
| 111 | cal.set(index, field[MAX_FIELD + index]); | ||
| 112 | break; | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | if (weekDate) { | ||
| 118 | int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1; | ||
| 119 | int dayOfWeek = isSet(DAY_OF_WEEK) ? | ||
| 120 | field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek(); | ||
| 121 | if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) { | ||
| 122 | if (dayOfWeek >= 8) { | ||
| 123 | dayOfWeek--; | ||
| 124 | weekOfYear += dayOfWeek / 7; | ||
| 125 | dayOfWeek = (dayOfWeek % 7) + 1; | ||
| 126 | } else { | ||
| 127 | while (dayOfWeek <= 0) { | ||
| 128 | dayOfWeek += 7; | ||
| 129 | weekOfYear--; | ||
| 130 | } | ||
| 131 | } | ||
| 132 | dayOfWeek = toCalendarDayOfWeek(dayOfWeek); | ||
| 133 | } | ||
| 134 | cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek); | ||
| 135 | } | ||
| 136 | return cal; | ||
| 137 | } | ||
| 138 | |||
| 139 | public String toString() { | ||
| 140 | StringBuilder sb = new StringBuilder(); | ||
| 141 | sb.append("CalendarBuilder:["); | ||
| 142 | for (int i = 0; i < field.length; i++) { | ||
| 143 | if (isSet(i)) { | ||
| 144 | sb.append(i).append('=').append(field[MAX_FIELD + i]).append(','); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | int lastIndex = sb.length() - 1; | ||
| 148 | if (sb.charAt(lastIndex) == ',') { | ||
| 149 | sb.setLength(lastIndex); | ||
| 150 | } | ||
| 151 | sb.append(']'); | ||
| 152 | return sb.toString(); | ||
| 153 | } | ||
| 154 | |||
| 155 | static int toISODayOfWeek(int calendarDayOfWeek) { | ||
| 156 | return calendarDayOfWeek == SUNDAY ? 7 : calendarDayOfWeek - 1; | ||
| 157 | } | ||
| 158 | |||
| 159 | static int toCalendarDayOfWeek(int isoDayOfWeek) { | ||
| 160 | if (!isValidDayOfWeek(isoDayOfWeek)) { | ||
| 161 | // adjust later for lenient mode | ||
| 162 | return isoDayOfWeek; | ||
| 163 | } | ||
| 164 | return isoDayOfWeek == 7 ? SUNDAY : isoDayOfWeek + 1; | ||
| 165 | } | ||
| 166 | |||
| 167 | static boolean isValidDayOfWeek(int dayOfWeek) { | ||
| 168 | return dayOfWeek > 0 && dayOfWeek <= 7; | ||
| 169 | } | ||
| 170 | } | ||
Binary file not shown.
| @@ -0,0 +1,193 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2000, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation | ||
| 31 | * is copyrighted and owned by Taligent, Inc., a wholly-owned | ||
| 32 | * subsidiary of IBM. These materials are provided under terms | ||
| 33 | * of a License Agreement between Taligent and Sun. This technology | ||
| 34 | * is protected by multiple US and International patents. | ||
| 35 | * | ||
| 36 | * This notice and attribution to Taligent may not be removed. | ||
| 37 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 38 | * | ||
| 39 | */ | ||
| 40 | |||
| 41 | package java.text; | ||
| 42 | |||
| 43 | |||
| 44 | /** | ||
| 45 | * This interface defines a protocol for bidirectional iteration over text. | ||
| 46 | * The iterator iterates over a bounded sequence of characters. Characters | ||
| 47 | * are indexed with values beginning with the value returned by getBeginIndex() and | ||
| 48 | * continuing through the value returned by getEndIndex()-1. | ||
| 49 | * <p> | ||
| 50 | * Iterators maintain a current character index, whose valid range is from | ||
| 51 | * getBeginIndex() to getEndIndex(); the value getEndIndex() is included to allow | ||
| 52 | * handling of zero-length text ranges and for historical reasons. | ||
| 53 | * The current index can be retrieved by calling getIndex() and set directly | ||
| 54 | * by calling setIndex(), first(), and last(). | ||
| 55 | * <p> | ||
| 56 | * The methods previous() and next() are used for iteration. They return DONE if | ||
| 57 | * they would move outside the range from getBeginIndex() to getEndIndex() -1, | ||
| 58 | * signaling that the iterator has reached the end of the sequence. DONE is | ||
| 59 | * also returned by other methods to indicate that the current index is | ||
| 60 | * outside this range. | ||
| 61 | * | ||
| 62 | * <P>Examples:<P> | ||
| 63 | * | ||
| 64 | * Traverse the text from start to finish | ||
| 65 | * <pre> | ||
| 66 | * public void traverseForward(CharacterIterator iter) { | ||
| 67 | * for(char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { | ||
| 68 | * processChar(c); | ||
| 69 | * } | ||
| 70 | * } | ||
| 71 | * </pre> | ||
| 72 | * | ||
| 73 | * Traverse the text backwards, from end to start | ||
| 74 | * <pre> | ||
| 75 | * public void traverseBackward(CharacterIterator iter) { | ||
| 76 | * for(char c = iter.last(); c != CharacterIterator.DONE; c = iter.previous()) { | ||
| 77 | * processChar(c); | ||
| 78 | * } | ||
| 79 | * } | ||
| 80 | * </pre> | ||
| 81 | * | ||
| 82 | * Traverse both forward and backward from a given position in the text. | ||
| 83 | * Calls to notBoundary() in this example represents some | ||
| 84 | * additional stopping criteria. | ||
| 85 | * <pre> | ||
| 86 | * public void traverseOut(CharacterIterator iter, int pos) { | ||
| 87 | * for (char c = iter.setIndex(pos); | ||
| 88 | * c != CharacterIterator.DONE && notBoundary(c); | ||
| 89 | * c = iter.next()) { | ||
| 90 | * } | ||
| 91 | * int end = iter.getIndex(); | ||
| 92 | * for (char c = iter.setIndex(pos); | ||
| 93 | * c != CharacterIterator.DONE && notBoundary(c); | ||
| 94 | * c = iter.previous()) { | ||
| 95 | * } | ||
| 96 | * int start = iter.getIndex(); | ||
| 97 | * processSection(start, end); | ||
| 98 | * } | ||
| 99 | * </pre> | ||
| 100 | * | ||
| 101 | * @see StringCharacterIterator | ||
| 102 | * @see AttributedCharacterIterator | ||
| 103 | */ | ||
| 104 | |||
| 105 | public interface CharacterIterator extends Cloneable | ||
| 106 | { | ||
| 107 | |||
| 108 | /** | ||
| 109 | * Constant that is returned when the iterator has reached either the end | ||
| 110 | * or the beginning of the text. The value is '\\uFFFF', the "not a | ||
| 111 | * character" value which should not occur in any valid Unicode string. | ||
| 112 | */ | ||
| 113 | public static final char DONE = '\uFFFF'; | ||
| 114 | |||
| 115 | /** | ||
| 116 | * Sets the position to getBeginIndex() and returns the character at that | ||
| 117 | * position. | ||
| 118 | * @return the first character in the text, or DONE if the text is empty | ||
| 119 | * @see #getBeginIndex() | ||
| 120 | */ | ||
| 121 | public char first(); | ||
| 122 | |||
| 123 | /** | ||
| 124 | * Sets the position to getEndIndex()-1 (getEndIndex() if the text is empty) | ||
| 125 | * and returns the character at that position. | ||
| 126 | * @return the last character in the text, or DONE if the text is empty | ||
| 127 | * @see #getEndIndex() | ||
| 128 | */ | ||
| 129 | public char last(); | ||
| 130 | |||
| 131 | /** | ||
| 132 | * Gets the character at the current position (as returned by getIndex()). | ||
| 133 | * @return the character at the current position or DONE if the current | ||
| 134 | * position is off the end of the text. | ||
| 135 | * @see #getIndex() | ||
| 136 | */ | ||
| 137 | public char current(); | ||
| 138 | |||
| 139 | /** | ||
| 140 | * Increments the iterator's index by one and returns the character | ||
| 141 | * at the new index. If the resulting index is greater or equal | ||
| 142 | * to getEndIndex(), the current index is reset to getEndIndex() and | ||
| 143 | * a value of DONE is returned. | ||
| 144 | * @return the character at the new position or DONE if the new | ||
| 145 | * position is off the end of the text range. | ||
| 146 | */ | ||
| 147 | public char next(); | ||
| 148 | |||
| 149 | /** | ||
| 150 | * Decrements the iterator's index by one and returns the character | ||
| 151 | * at the new index. If the current index is getBeginIndex(), the index | ||
| 152 | * remains at getBeginIndex() and a value of DONE is returned. | ||
| 153 | * @return the character at the new position or DONE if the current | ||
| 154 | * position is equal to getBeginIndex(). | ||
| 155 | */ | ||
| 156 | public char previous(); | ||
| 157 | |||
| 158 | /** | ||
| 159 | * Sets the position to the specified position in the text and returns that | ||
| 160 | * character. | ||
| 161 | * @param position the position within the text. Valid values range from | ||
| 162 | * getBeginIndex() to getEndIndex(). An IllegalArgumentException is thrown | ||
| 163 | * if an invalid value is supplied. | ||
| 164 | * @return the character at the specified position or DONE if the specified position is equal to getEndIndex() | ||
| 165 | */ | ||
| 166 | public char setIndex(int position); | ||
| 167 | |||
| 168 | /** | ||
| 169 | * Returns the start index of the text. | ||
| 170 | * @return the index at which the text begins. | ||
| 171 | */ | ||
| 172 | public int getBeginIndex(); | ||
| 173 | |||
| 174 | /** | ||
| 175 | * Returns the end index of the text. This index is the index of the first | ||
| 176 | * character following the end of the text. | ||
| 177 | * @return the index after the last character in the text | ||
| 178 | */ | ||
| 179 | public int getEndIndex(); | ||
| 180 | |||
| 181 | /** | ||
| 182 | * Returns the current index. | ||
| 183 | * @return the current index. | ||
| 184 | */ | ||
| 185 | public int getIndex(); | ||
| 186 | |||
| 187 | /** | ||
| 188 | * Create a copy of this iterator | ||
| 189 | * @return A copy of this | ||
| 190 | */ | ||
| 191 | public Object clone(); | ||
| 192 | |||
| 193 | } | ||
Binary file not shown.
| @@ -0,0 +1,124 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | package java.text; | ||
| 26 | |||
| 27 | import java.util.ArrayList; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * CharacterIteratorFieldDelegate combines the notifications from a Format | ||
| 31 | * into a resulting <code>AttributedCharacterIterator</code>. The resulting | ||
| 32 | * <code>AttributedCharacterIterator</code> can be retrieved by way of | ||
| 33 | * the <code>getIterator</code> method. | ||
| 34 | * | ||
| 35 | */ | ||
| 36 | class CharacterIteratorFieldDelegate implements Format.FieldDelegate { | ||
| 37 | /** | ||
| 38 | * Array of AttributeStrings. Whenever <code>formatted</code> is invoked | ||
| 39 | * for a region > size, a new instance of AttributedString is added to | ||
| 40 | * attributedStrings. Subsequent invocations of <code>formatted</code> | ||
| 41 | * for existing regions result in invoking addAttribute on the existing | ||
| 42 | * AttributedStrings. | ||
| 43 | */ | ||
| 44 | private ArrayList attributedStrings; | ||
| 45 | /** | ||
| 46 | * Running count of the number of characters that have | ||
| 47 | * been encountered. | ||
| 48 | */ | ||
| 49 | private int size; | ||
| 50 | |||
| 51 | |||
| 52 | CharacterIteratorFieldDelegate() { | ||
| 53 | attributedStrings = new ArrayList(); | ||
| 54 | } | ||
| 55 | |||
| 56 | public void formatted(Format.Field attr, Object value, int start, int end, | ||
| 57 | StringBuffer buffer) { | ||
| 58 | if (start != end) { | ||
| 59 | if (start < size) { | ||
| 60 | // Adjust attributes of existing runs | ||
| 61 | int index = size; | ||
| 62 | int asIndex = attributedStrings.size() - 1; | ||
| 63 | |||
| 64 | while (start < index) { | ||
| 65 | AttributedString as = (AttributedString)attributedStrings. | ||
| 66 | get(asIndex--); | ||
| 67 | int newIndex = index - as.length(); | ||
| 68 | int aStart = Math.max(0, start - newIndex); | ||
| 69 | |||
| 70 | as.addAttribute(attr, value, aStart, Math.min( | ||
| 71 | end - start, as.length() - aStart) + | ||
| 72 | aStart); | ||
| 73 | index = newIndex; | ||
| 74 | } | ||
| 75 | } | ||
| 76 | if (size < start) { | ||
| 77 | // Pad attributes | ||
| 78 | attributedStrings.add(new AttributedString( | ||
| 79 | buffer.substring(size, start))); | ||
| 80 | size = start; | ||
| 81 | } | ||
| 82 | if (size < end) { | ||
| 83 | // Add new string | ||
| 84 | int aStart = Math.max(start, size); | ||
| 85 | AttributedString string = new AttributedString( | ||
| 86 | buffer.substring(aStart, end)); | ||
| 87 | |||
| 88 | string.addAttribute(attr, value); | ||
| 89 | attributedStrings.add(string); | ||
| 90 | size = end; | ||
| 91 | } | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | public void formatted(int fieldID, Format.Field attr, Object value, | ||
| 96 | int start, int end, StringBuffer buffer) { | ||
| 97 | formatted(attr, value, start, end, buffer); | ||
| 98 | } | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Returns an <code>AttributedCharacterIterator</code> that can be used | ||
| 102 | * to iterate over the resulting formatted String. | ||
| 103 | * | ||
| 104 | * @pararm string Result of formatting. | ||
| 105 | */ | ||
| 106 | public AttributedCharacterIterator getIterator(String string) { | ||
| 107 | // Add the last AttributedCharacterIterator if necessary | ||
| 108 | // assert(size <= string.length()); | ||
| 109 | if (string.length() > size) { | ||
| 110 | attributedStrings.add(new AttributedString( | ||
| 111 | string.substring(size))); | ||
| 112 | size = string.length(); | ||
| 113 | } | ||
| 114 | int iCount = attributedStrings.size(); | ||
| 115 | AttributedCharacterIterator iterators[] = new | ||
| 116 | AttributedCharacterIterator[iCount]; | ||
| 117 | |||
| 118 | for (int counter = 0; counter < iCount; counter++) { | ||
| 119 | iterators[counter] = ((AttributedString)attributedStrings. | ||
| 120 | get(counter)).getIterator(); | ||
| 121 | } | ||
| 122 | return new AttributedString(iterators).getIterator(); | ||
| 123 | } | ||
| 124 | } | ||
Binary file not shown.
| @@ -0,0 +1,619 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2005, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.io.InvalidObjectException; | ||
| 42 | import java.io.IOException; | ||
| 43 | import java.io.ObjectInputStream; | ||
| 44 | import java.util.Arrays; | ||
| 45 | |||
| 46 | /** | ||
| 47 | * A <code>ChoiceFormat</code> allows you to attach a format to a range of numbers. | ||
| 48 | * It is generally used in a <code>MessageFormat</code> for handling plurals. | ||
| 49 | * The choice is specified with an ascending list of doubles, where each item | ||
| 50 | * specifies a half-open interval up to the next item: | ||
| 51 | * <blockquote> | ||
| 52 | * <pre> | ||
| 53 | * X matches j if and only if limit[j] <= X < limit[j+1] | ||
| 54 | * </pre> | ||
| 55 | * </blockquote> | ||
| 56 | * If there is no match, then either the first or last index is used, depending | ||
| 57 | * on whether the number (X) is too low or too high. If the limit array is not | ||
| 58 | * in ascending order, the results of formatting will be incorrect. ChoiceFormat | ||
| 59 | * also accepts <code>\u221E</code> as equivalent to infinity(INF). | ||
| 60 | * | ||
| 61 | * <p> | ||
| 62 | * <strong>Note:</strong> | ||
| 63 | * <code>ChoiceFormat</code> differs from the other <code>Format</code> | ||
| 64 | * classes in that you create a <code>ChoiceFormat</code> object with a | ||
| 65 | * constructor (not with a <code>getInstance</code> style factory | ||
| 66 | * method). The factory methods aren't necessary because <code>ChoiceFormat</code> | ||
| 67 | * doesn't require any complex setup for a given locale. In fact, | ||
| 68 | * <code>ChoiceFormat</code> doesn't implement any locale specific behavior. | ||
| 69 | * | ||
| 70 | * <p> | ||
| 71 | * When creating a <code>ChoiceFormat</code>, you must specify an array of formats | ||
| 72 | * and an array of limits. The length of these arrays must be the same. | ||
| 73 | * For example, | ||
| 74 | * <ul> | ||
| 75 | * <li> | ||
| 76 | * <em>limits</em> = {1,2,3,4,5,6,7}<br> | ||
| 77 | * <em>formats</em> = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"} | ||
| 78 | * <li> | ||
| 79 | * <em>limits</em> = {0, 1, ChoiceFormat.nextDouble(1)}<br> | ||
| 80 | * <em>formats</em> = {"no files", "one file", "many files"}<br> | ||
| 81 | * (<code>nextDouble</code> can be used to get the next higher double, to | ||
| 82 | * make the half-open interval.) | ||
| 83 | * </ul> | ||
| 84 | * | ||
| 85 | * <p> | ||
| 86 | * Here is a simple example that shows formatting and parsing: | ||
| 87 | * <blockquote> | ||
| 88 | * <pre> | ||
| 89 | * double[] limits = {1,2,3,4,5,6,7}; | ||
| 90 | * String[] dayOfWeekNames = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"}; | ||
| 91 | * ChoiceFormat form = new ChoiceFormat(limits, dayOfWeekNames); | ||
| 92 | * ParsePosition status = new ParsePosition(0); | ||
| 93 | * for (double i = 0.0; i <= 8.0; ++i) { | ||
| 94 | * status.setIndex(0); | ||
| 95 | * System.out.println(i + " -> " + form.format(i) + " -> " | ||
| 96 | * + form.parse(form.format(i),status)); | ||
| 97 | * } | ||
| 98 | * </pre> | ||
| 99 | * </blockquote> | ||
| 100 | * Here is a more complex example, with a pattern format: | ||
| 101 | * <blockquote> | ||
| 102 | * <pre> | ||
| 103 | * double[] filelimits = {0,1,2}; | ||
| 104 | * String[] filepart = {"are no files","is one file","are {2} files"}; | ||
| 105 | * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart); | ||
| 106 | * Format[] testFormats = {fileform, null, NumberFormat.getInstance()}; | ||
| 107 | * MessageFormat pattform = new MessageFormat("There {0} on {1}"); | ||
| 108 | * pattform.setFormats(testFormats); | ||
| 109 | * Object[] testArgs = {null, "ADisk", null}; | ||
| 110 | * for (int i = 0; i < 4; ++i) { | ||
| 111 | * testArgs[0] = new Integer(i); | ||
| 112 | * testArgs[2] = testArgs[0]; | ||
| 113 | * System.out.println(pattform.format(testArgs)); | ||
| 114 | * } | ||
| 115 | * </pre> | ||
| 116 | * </blockquote> | ||
| 117 | * <p> | ||
| 118 | * Specifying a pattern for ChoiceFormat objects is fairly straightforward. | ||
| 119 | * For example: | ||
| 120 | * <blockquote> | ||
| 121 | * <pre> | ||
| 122 | * ChoiceFormat fmt = new ChoiceFormat( | ||
| 123 | * "-1#is negative| 0#is zero or fraction | 1#is one |1.0<is 1+ |2#is two |2<is more than 2."); | ||
| 124 | * System.out.println("Formatter Pattern : " + fmt.toPattern()); | ||
| 125 | * | ||
| 126 | * System.out.println("Format with -INF : " + fmt.format(Double.NEGATIVE_INFINITY)); | ||
| 127 | * System.out.println("Format with -1.0 : " + fmt.format(-1.0)); | ||
| 128 | * System.out.println("Format with 0 : " + fmt.format(0)); | ||
| 129 | * System.out.println("Format with 0.9 : " + fmt.format(0.9)); | ||
| 130 | * System.out.println("Format with 1.0 : " + fmt.format(1)); | ||
| 131 | * System.out.println("Format with 1.5 : " + fmt.format(1.5)); | ||
| 132 | * System.out.println("Format with 2 : " + fmt.format(2)); | ||
| 133 | * System.out.println("Format with 2.1 : " + fmt.format(2.1)); | ||
| 134 | * System.out.println("Format with NaN : " + fmt.format(Double.NaN)); | ||
| 135 | * System.out.println("Format with +INF : " + fmt.format(Double.POSITIVE_INFINITY)); | ||
| 136 | * </pre> | ||
| 137 | * </blockquote> | ||
| 138 | * And the output result would be like the following: | ||
| 139 | * <blockquote> | ||
| 140 | * <pre> | ||
| 141 | * Format with -INF : is negative | ||
| 142 | * Format with -1.0 : is negative | ||
| 143 | * Format with 0 : is zero or fraction | ||
| 144 | * Format with 0.9 : is zero or fraction | ||
| 145 | * Format with 1.0 : is one | ||
| 146 | * Format with 1.5 : is 1+ | ||
| 147 | * Format with 2 : is two | ||
| 148 | * Format with 2.1 : is more than 2. | ||
| 149 | * Format with NaN : is negative | ||
| 150 | * Format with +INF : is more than 2. | ||
| 151 | * </pre> | ||
| 152 | * </blockquote> | ||
| 153 | * | ||
| 154 | * <h4><a name="synchronization">Synchronization</a></h4> | ||
| 155 | * | ||
| 156 | * <p> | ||
| 157 | * Choice formats are not synchronized. | ||
| 158 | * It is recommended to create separate format instances for each thread. | ||
| 159 | * If multiple threads access a format concurrently, it must be synchronized | ||
| 160 | * externally. | ||
| 161 | * | ||
| 162 | * | ||
| 163 | * @see DecimalFormat | ||
| 164 | * @see MessageFormat | ||
| 165 | * @author Mark Davis | ||
| 166 | */ | ||
| 167 | public class ChoiceFormat extends NumberFormat { | ||
| 168 | |||
| 169 | // Proclaim serial compatibility with 1.1 FCS | ||
| 170 | private static final long serialVersionUID = 1795184449645032964L; | ||
| 171 | |||
| 172 | /** | ||
| 173 | * Sets the pattern. | ||
| 174 | * @param newPattern See the class description. | ||
| 175 | */ | ||
| 176 | public void applyPattern(String newPattern) { | ||
| 177 | StringBuffer[] segments = new StringBuffer[2]; | ||
| 178 | for (int i = 0; i < segments.length; ++i) { | ||
| 179 | segments[i] = new StringBuffer(); | ||
| 180 | } | ||
| 181 | double[] newChoiceLimits = new double[30]; | ||
| 182 | String[] newChoiceFormats = new String[30]; | ||
| 183 | int count = 0; | ||
| 184 | int part = 0; | ||
| 185 | double startValue = 0; | ||
| 186 | double oldStartValue = Double.NaN; | ||
| 187 | boolean inQuote = false; | ||
| 188 | for (int i = 0; i < newPattern.length(); ++i) { | ||
| 189 | char ch = newPattern.charAt(i); | ||
| 190 | if (ch=='\'') { | ||
| 191 | // Check for "''" indicating a literal quote | ||
| 192 | if ((i+1)<newPattern.length() && newPattern.charAt(i+1)==ch) { | ||
| 193 | segments[part].append(ch); | ||
| 194 | ++i; | ||
| 195 | } else { | ||
| 196 | inQuote = !inQuote; | ||
| 197 | } | ||
| 198 | } else if (inQuote) { | ||
| 199 | segments[part].append(ch); | ||
| 200 | } else if (ch == '<' || ch == '#' || ch == '\u2264') { | ||
| 201 | if (segments[0].length() == 0) { | ||
| 202 | throw new IllegalArgumentException(); | ||
| 203 | } | ||
| 204 | try { | ||
| 205 | String tempBuffer = segments[0].toString(); | ||
| 206 | if (tempBuffer.equals("\u221E")) { | ||
| 207 | startValue = Double.POSITIVE_INFINITY; | ||
| 208 | } else if (tempBuffer.equals("-\u221E")) { | ||
| 209 | startValue = Double.NEGATIVE_INFINITY; | ||
| 210 | } else { | ||
| 211 | startValue = Double.valueOf(segments[0].toString()).doubleValue(); | ||
| 212 | } | ||
| 213 | } catch (Exception e) { | ||
| 214 | throw new IllegalArgumentException(); | ||
| 215 | } | ||
| 216 | if (ch == '<' && startValue != Double.POSITIVE_INFINITY && | ||
| 217 | startValue != Double.NEGATIVE_INFINITY) { | ||
| 218 | startValue = nextDouble(startValue); | ||
| 219 | } | ||
| 220 | if (startValue <= oldStartValue) { | ||
| 221 | throw new IllegalArgumentException(); | ||
| 222 | } | ||
| 223 | segments[0].setLength(0); | ||
| 224 | part = 1; | ||
| 225 | } else if (ch == '|') { | ||
| 226 | if (count == newChoiceLimits.length) { | ||
| 227 | newChoiceLimits = doubleArraySize(newChoiceLimits); | ||
| 228 | newChoiceFormats = doubleArraySize(newChoiceFormats); | ||
| 229 | } | ||
| 230 | newChoiceLimits[count] = startValue; | ||
| 231 | newChoiceFormats[count] = segments[1].toString(); | ||
| 232 | ++count; | ||
| 233 | oldStartValue = startValue; | ||
| 234 | segments[1].setLength(0); | ||
| 235 | part = 0; | ||
| 236 | } else { | ||
| 237 | segments[part].append(ch); | ||
| 238 | } | ||
| 239 | } | ||
| 240 | // clean up last one | ||
| 241 | if (part == 1) { | ||
| 242 | if (count == newChoiceLimits.length) { | ||
| 243 | newChoiceLimits = doubleArraySize(newChoiceLimits); | ||
| 244 | newChoiceFormats = doubleArraySize(newChoiceFormats); | ||
| 245 | } | ||
| 246 | newChoiceLimits[count] = startValue; | ||
| 247 | newChoiceFormats[count] = segments[1].toString(); | ||
| 248 | ++count; | ||
| 249 | } | ||
| 250 | choiceLimits = new double[count]; | ||
| 251 | System.arraycopy(newChoiceLimits, 0, choiceLimits, 0, count); | ||
| 252 | choiceFormats = new String[count]; | ||
| 253 | System.arraycopy(newChoiceFormats, 0, choiceFormats, 0, count); | ||
| 254 | } | ||
| 255 | |||
| 256 | /** | ||
| 257 | * Gets the pattern. | ||
| 258 | */ | ||
| 259 | public String toPattern() { | ||
| 260 | StringBuffer result = new StringBuffer(); | ||
| 261 | for (int i = 0; i < choiceLimits.length; ++i) { | ||
| 262 | if (i != 0) { | ||
| 263 | result.append('|'); | ||
| 264 | } | ||
| 265 | // choose based upon which has less precision | ||
| 266 | // approximate that by choosing the closest one to an integer. | ||
| 267 | // could do better, but it's not worth it. | ||
| 268 | double less = previousDouble(choiceLimits[i]); | ||
| 269 | double tryLessOrEqual = Math.abs(Math.IEEEremainder(choiceLimits[i], 1.0d)); | ||
| 270 | double tryLess = Math.abs(Math.IEEEremainder(less, 1.0d)); | ||
| 271 | |||
| 272 | if (tryLessOrEqual < tryLess) { | ||
| 273 | result.append(""+choiceLimits[i]); | ||
| 274 | result.append('#'); | ||
| 275 | } else { | ||
| 276 | if (choiceLimits[i] == Double.POSITIVE_INFINITY) { | ||
| 277 | result.append("\u221E"); | ||
| 278 | } else if (choiceLimits[i] == Double.NEGATIVE_INFINITY) { | ||
| 279 | result.append("-\u221E"); | ||
| 280 | } else { | ||
| 281 | result.append(""+less); | ||
| 282 | } | ||
| 283 | result.append('<'); | ||
| 284 | } | ||
| 285 | // Append choiceFormats[i], using quotes if there are special characters. | ||
| 286 | // Single quotes themselves must be escaped in either case. | ||
| 287 | String text = choiceFormats[i]; | ||
| 288 | boolean needQuote = text.indexOf('<') >= 0 | ||
| 289 | || text.indexOf('#') >= 0 | ||
| 290 | || text.indexOf('\u2264') >= 0 | ||
| 291 | || text.indexOf('|') >= 0; | ||
| 292 | if (needQuote) result.append('\''); | ||
| 293 | if (text.indexOf('\'') < 0) result.append(text); | ||
| 294 | else { | ||
| 295 | for (int j=0; j<text.length(); ++j) { | ||
| 296 | char c = text.charAt(j); | ||
| 297 | result.append(c); | ||
| 298 | if (c == '\'') result.append(c); | ||
| 299 | } | ||
| 300 | } | ||
| 301 | if (needQuote) result.append('\''); | ||
| 302 | } | ||
| 303 | return result.toString(); | ||
| 304 | } | ||
| 305 | |||
| 306 | /** | ||
| 307 | * Constructs with limits and corresponding formats based on the pattern. | ||
| 308 | * @see #applyPattern | ||
| 309 | */ | ||
| 310 | public ChoiceFormat(String newPattern) { | ||
| 311 | applyPattern(newPattern); | ||
| 312 | } | ||
| 313 | |||
| 314 | /** | ||
| 315 | * Constructs with the limits and the corresponding formats. | ||
| 316 | * @see #setChoices | ||
| 317 | */ | ||
| 318 | public ChoiceFormat(double[] limits, String[] formats) { | ||
| 319 | setChoices(limits, formats); | ||
| 320 | } | ||
| 321 | |||
| 322 | /** | ||
| 323 | * Set the choices to be used in formatting. | ||
| 324 | * @param limits contains the top value that you want | ||
| 325 | * parsed with that format,and should be in ascending sorted order. When | ||
| 326 | * formatting X, the choice will be the i, where | ||
| 327 | * limit[i] <= X < limit[i+1]. | ||
| 328 | * If the limit array is not in ascending order, the results of formatting | ||
| 329 | * will be incorrect. | ||
| 330 | * @param formats are the formats you want to use for each limit. | ||
| 331 | * They can be either Format objects or Strings. | ||
| 332 | * When formatting with object Y, | ||
| 333 | * if the object is a NumberFormat, then ((NumberFormat) Y).format(X) | ||
| 334 | * is called. Otherwise Y.toString() is called. | ||
| 335 | */ | ||
| 336 | public void setChoices(double[] limits, String formats[]) { | ||
| 337 | if (limits.length != formats.length) { | ||
| 338 | throw new IllegalArgumentException( | ||
| 339 | "Array and limit arrays must be of the same length."); | ||
| 340 | } | ||
| 341 | choiceLimits = limits; | ||
| 342 | choiceFormats = formats; | ||
| 343 | } | ||
| 344 | |||
| 345 | /** | ||
| 346 | * Get the limits passed in the constructor. | ||
| 347 | * @return the limits. | ||
| 348 | */ | ||
| 349 | public double[] getLimits() { | ||
| 350 | return choiceLimits; | ||
| 351 | } | ||
| 352 | |||
| 353 | /** | ||
| 354 | * Get the formats passed in the constructor. | ||
| 355 | * @return the formats. | ||
| 356 | */ | ||
| 357 | public Object[] getFormats() { | ||
| 358 | return choiceFormats; | ||
| 359 | } | ||
| 360 | |||
| 361 | // Overrides | ||
| 362 | |||
| 363 | /** | ||
| 364 | * Specialization of format. This method really calls | ||
| 365 | * <code>format(double, StringBuffer, FieldPosition)</code> | ||
| 366 | * thus the range of longs that are supported is only equal to | ||
| 367 | * the range that can be stored by double. This will never be | ||
| 368 | * a practical limitation. | ||
| 369 | */ | ||
| 370 | public StringBuffer format(long number, StringBuffer toAppendTo, | ||
| 371 | FieldPosition status) { | ||
| 372 | return format((double)number, toAppendTo, status); | ||
| 373 | } | ||
| 374 | |||
| 375 | /** | ||
| 376 | * Returns pattern with formatted double. | ||
| 377 | * @param number number to be formatted & substituted. | ||
| 378 | * @param toAppendTo where text is appended. | ||
| 379 | * @param status ignore no useful status is returned. | ||
| 380 | */ | ||
| 381 | public StringBuffer format(double number, StringBuffer toAppendTo, | ||
| 382 | FieldPosition status) { | ||
| 383 | // find the number | ||
| 384 | int i; | ||
| 385 | for (i = 0; i < choiceLimits.length; ++i) { | ||
| 386 | if (!(number >= choiceLimits[i])) { | ||
| 387 | // same as number < choiceLimits, except catchs NaN | ||
| 388 | break; | ||
| 389 | } | ||
| 390 | } | ||
| 391 | --i; | ||
| 392 | if (i < 0) i = 0; | ||
| 393 | // return either a formatted number, or a string | ||
| 394 | return toAppendTo.append(choiceFormats[i]); | ||
| 395 | } | ||
| 396 | |||
| 397 | /** | ||
| 398 | * Parses a Number from the input text. | ||
| 399 | * @param text the source text. | ||
| 400 | * @param status an input-output parameter. On input, the | ||
| 401 | * status.index field indicates the first character of the | ||
| 402 | * source text that should be parsed. On exit, if no error | ||
| 403 | * occured, status.index is set to the first unparsed character | ||
| 404 | * in the source text. On exit, if an error did occur, | ||
| 405 | * status.index is unchanged and status.errorIndex is set to the | ||
| 406 | * first index of the character that caused the parse to fail. | ||
| 407 | * @return A Number representing the value of the number parsed. | ||
| 408 | */ | ||
| 409 | public Number parse(String text, ParsePosition status) { | ||
| 410 | // find the best number (defined as the one with the longest parse) | ||
| 411 | int start = status.index; | ||
| 412 | int furthest = start; | ||
| 413 | double bestNumber = Double.NaN; | ||
| 414 | double tempNumber = 0.0; | ||
| 415 | for (int i = 0; i < choiceFormats.length; ++i) { | ||
| 416 | String tempString = choiceFormats[i]; | ||
| 417 | if (text.regionMatches(start, tempString, 0, tempString.length())) { | ||
| 418 | status.index = start + tempString.length(); | ||
| 419 | tempNumber = choiceLimits[i]; | ||
| 420 | if (status.index > furthest) { | ||
| 421 | furthest = status.index; | ||
| 422 | bestNumber = tempNumber; | ||
| 423 | if (furthest == text.length()) break; | ||
| 424 | } | ||
| 425 | } | ||
| 426 | } | ||
| 427 | status.index = furthest; | ||
| 428 | if (status.index == start) { | ||
| 429 | status.errorIndex = furthest; | ||
| 430 | } | ||
| 431 | return new Double(bestNumber); | ||
| 432 | } | ||
| 433 | |||
| 434 | /** | ||
| 435 | * Finds the least double greater than d. | ||
| 436 | * If NaN, returns same value. | ||
| 437 | * <p>Used to make half-open intervals. | ||
| 438 | * @see #previousDouble | ||
| 439 | */ | ||
| 440 | public static final double nextDouble (double d) { | ||
| 441 | return nextDouble(d,true); | ||
| 442 | } | ||
| 443 | |||
| 444 | /** | ||
| 445 | * Finds the greatest double less than d. | ||
| 446 | * If NaN, returns same value. | ||
| 447 | * @see #nextDouble | ||
| 448 | */ | ||
| 449 | public static final double previousDouble (double d) { | ||
| 450 | return nextDouble(d,false); | ||
| 451 | } | ||
| 452 | |||
| 453 | /** | ||
| 454 | * Overrides Cloneable | ||
| 455 | */ | ||
| 456 | public Object clone() | ||
| 457 | { | ||
| 458 | ChoiceFormat other = (ChoiceFormat) super.clone(); | ||
| 459 | // for primitives or immutables, shallow clone is enough | ||
| 460 | other.choiceLimits = (double[]) choiceLimits.clone(); | ||
| 461 | other.choiceFormats = (String[]) choiceFormats.clone(); | ||
| 462 | return other; | ||
| 463 | } | ||
| 464 | |||
| 465 | /** | ||
| 466 | * Generates a hash code for the message format object. | ||
| 467 | */ | ||
| 468 | public int hashCode() { | ||
| 469 | int result = choiceLimits.length; | ||
| 470 | if (choiceFormats.length > 0) { | ||
| 471 | // enough for reasonable distribution | ||
| 472 | result ^= choiceFormats[choiceFormats.length-1].hashCode(); | ||
| 473 | } | ||
| 474 | return result; | ||
| 475 | } | ||
| 476 | |||
| 477 | /** | ||
| 478 | * Equality comparision between two | ||
| 479 | */ | ||
| 480 | public boolean equals(Object obj) { | ||
| 481 | if (obj == null) return false; | ||
| 482 | if (this == obj) // quick check | ||
| 483 | return true; | ||
| 484 | if (getClass() != obj.getClass()) | ||
| 485 | return false; | ||
| 486 | ChoiceFormat other = (ChoiceFormat) obj; | ||
| 487 | return (Arrays.equals(choiceLimits, other.choiceLimits) | ||
| 488 | && Arrays.equals(choiceFormats, other.choiceFormats)); | ||
| 489 | } | ||
| 490 | |||
| 491 | /** | ||
| 492 | * After reading an object from the input stream, do a simple verification | ||
| 493 | * to maintain class invariants. | ||
| 494 | * @throws InvalidObjectException if the objects read from the stream is invalid. | ||
| 495 | */ | ||
| 496 | private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { | ||
| 497 | in.defaultReadObject(); | ||
| 498 | if (choiceLimits.length != choiceFormats.length) { | ||
| 499 | throw new InvalidObjectException( | ||
| 500 | "limits and format arrays of different length."); | ||
| 501 | } | ||
| 502 | } | ||
| 503 | |||
| 504 | // ===============privates=========================== | ||
| 505 | |||
| 506 | /** | ||
| 507 | * A list of lower bounds for the choices. The formatter will return | ||
| 508 | * <code>choiceFormats[i]</code> if the number being formatted is greater than or equal to | ||
| 509 | * <code>choiceLimits[i]</code> and less than <code>choiceLimits[i+1]</code>. | ||
| 510 | * @serial | ||
| 511 | */ | ||
| 512 | private double[] choiceLimits; | ||
| 513 | |||
| 514 | /** | ||
| 515 | * A list of choice strings. The formatter will return | ||
| 516 | * <code>choiceFormats[i]</code> if the number being formatted is greater than or equal to | ||
| 517 | * <code>choiceLimits[i]</code> and less than <code>choiceLimits[i+1]</code>. | ||
| 518 | * @serial | ||
| 519 | */ | ||
| 520 | private String[] choiceFormats; | ||
| 521 | |||
| 522 | /* | ||
| 523 | static final long SIGN = 0x8000000000000000L; | ||
| 524 | static final long EXPONENT = 0x7FF0000000000000L; | ||
| 525 | static final long SIGNIFICAND = 0x000FFFFFFFFFFFFFL; | ||
| 526 | |||
| 527 | private static double nextDouble (double d, boolean positive) { | ||
| 528 | if (Double.isNaN(d) || Double.isInfinite(d)) { | ||
| 529 | return d; | ||
| 530 | } | ||
| 531 | long bits = Double.doubleToLongBits(d); | ||
| 532 | long significand = bits & SIGNIFICAND; | ||
| 533 | if (bits < 0) { | ||
| 534 | significand |= (SIGN | EXPONENT); | ||
| 535 | } | ||
| 536 | long exponent = bits & EXPONENT; | ||
| 537 | if (positive) { | ||
| 538 | significand += 1; | ||
| 539 | // FIXME fix overflow & underflow | ||
| 540 | } else { | ||
| 541 | significand -= 1; | ||
| 542 | // FIXME fix overflow & underflow | ||
| 543 | } | ||
| 544 | bits = exponent | (significand & ~EXPONENT); | ||
| 545 | return Double.longBitsToDouble(bits); | ||
| 546 | } | ||
| 547 | */ | ||
| 548 | |||
| 549 | static final long SIGN = 0x8000000000000000L; | ||
| 550 | static final long EXPONENT = 0x7FF0000000000000L; | ||
| 551 | static final long POSITIVEINFINITY = 0x7FF0000000000000L; | ||
| 552 | |||
| 553 | /** | ||
| 554 | * Finds the least double greater than d (if positive == true), | ||
| 555 | * or the greatest double less than d (if positive == false). | ||
| 556 | * If NaN, returns same value. | ||
| 557 | * | ||
| 558 | * Does not affect floating-point flags, | ||
| 559 | * provided these member functions do not: | ||
| 560 | * Double.longBitsToDouble(long) | ||
| 561 | * Double.doubleToLongBits(double) | ||
| 562 | * Double.isNaN(double) | ||
| 563 | */ | ||
| 564 | public static double nextDouble (double d, boolean positive) { | ||
| 565 | |||
| 566 | /* filter out NaN's */ | ||
| 567 | if (Double.isNaN(d)) { | ||
| 568 | return d; | ||
| 569 | } | ||
| 570 | |||
| 571 | /* zero's are also a special case */ | ||
| 572 | if (d == 0.0) { | ||
| 573 | double smallestPositiveDouble = Double.longBitsToDouble(1L); | ||
| 574 | if (positive) { | ||
| 575 | return smallestPositiveDouble; | ||
| 576 | } else { | ||
| 577 | return -smallestPositiveDouble; | ||
| 578 | } | ||
| 579 | } | ||
| 580 | |||
| 581 | /* if entering here, d is a nonzero value */ | ||
| 582 | |||
| 583 | /* hold all bits in a long for later use */ | ||
| 584 | long bits = Double.doubleToLongBits(d); | ||
| 585 | |||
| 586 | /* strip off the sign bit */ | ||
| 587 | long magnitude = bits & ~SIGN; | ||
| 588 | |||
| 589 | /* if next double away from zero, increase magnitude */ | ||
| 590 | if ((bits > 0) == positive) { | ||
| 591 | if (magnitude != POSITIVEINFINITY) { | ||
| 592 | magnitude += 1; | ||
| 593 | } | ||
| 594 | } | ||
| 595 | /* else decrease magnitude */ | ||
| 596 | else { | ||
| 597 | magnitude -= 1; | ||
| 598 | } | ||
| 599 | |||
| 600 | /* restore sign bit and return */ | ||
| 601 | long signbit = bits & SIGN; | ||
| 602 | return Double.longBitsToDouble (magnitude | signbit); | ||
| 603 | } | ||
| 604 | |||
| 605 | private static double[] doubleArraySize(double[] array) { | ||
| 606 | int oldSize = array.length; | ||
| 607 | double[] newArray = new double[oldSize * 2]; | ||
| 608 | System.arraycopy(array, 0, newArray, 0, oldSize); | ||
| 609 | return newArray; | ||
| 610 | } | ||
| 611 | |||
| 612 | private String[] doubleArraySize(String[] array) { | ||
| 613 | int oldSize = array.length; | ||
| 614 | String[] newArray = new String[oldSize * 2]; | ||
| 615 | System.arraycopy(array, 0, newArray, 0, oldSize); | ||
| 616 | return newArray; | ||
| 617 | } | ||
| 618 | |||
| 619 | } | ||
Binary file not shown.
| @@ -0,0 +1,777 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.lang.Character; | ||
| 42 | import java.util.Vector; | ||
| 43 | import sun.text.CollatorUtilities; | ||
| 44 | import sun.text.normalizer.NormalizerBase; | ||
| 45 | |||
| 46 | /** | ||
| 47 | * The <code>CollationElementIterator</code> class is used as an iterator | ||
| 48 | * to walk through each character of an international string. Use the iterator | ||
| 49 | * to return the ordering priority of the positioned character. The ordering | ||
| 50 | * priority of a character, which we refer to as a key, defines how a character | ||
| 51 | * is collated in the given collation object. | ||
| 52 | * | ||
| 53 | * <p> | ||
| 54 | * For example, consider the following in Spanish: | ||
| 55 | * <blockquote> | ||
| 56 | * <pre> | ||
| 57 | * "ca" -> the first key is key('c') and second key is key('a'). | ||
| 58 | * "cha" -> the first key is key('ch') and second key is key('a'). | ||
| 59 | * </pre> | ||
| 60 | * </blockquote> | ||
| 61 | * And in German, | ||
| 62 | * <blockquote> | ||
| 63 | * <pre> | ||
| 64 | * "\u00e4b"-> the first key is key('a'), the second key is key('e'), and | ||
| 65 | * the third key is key('b'). | ||
| 66 | * </pre> | ||
| 67 | * </blockquote> | ||
| 68 | * The key of a character is an integer composed of primary order(short), | ||
| 69 | * secondary order(byte), and tertiary order(byte). Java strictly defines | ||
| 70 | * the size and signedness of its primitive data types. Therefore, the static | ||
| 71 | * functions <code>primaryOrder</code>, <code>secondaryOrder</code>, and | ||
| 72 | * <code>tertiaryOrder</code> return <code>int</code>, <code>short</code>, | ||
| 73 | * and <code>short</code> respectively to ensure the correctness of the key | ||
| 74 | * value. | ||
| 75 | * | ||
| 76 | * <p> | ||
| 77 | * Example of the iterator usage, | ||
| 78 | * <blockquote> | ||
| 79 | * <pre> | ||
| 80 | * | ||
| 81 | * String testString = "This is a test"; | ||
| 82 | * Collator col = Collator.getInstance(); | ||
| 83 | * if (col instanceof RuleBasedCollator) { | ||
| 84 | * RuleBasedCollator ruleBasedCollator = (RuleBasedCollator)col; | ||
| 85 | * CollationElementIterator collationElementIterator = ruleBasedCollator.getCollationElementIterator(testString); | ||
| 86 | * int primaryOrder = CollationElementIterator.primaryOrder(collationElementIterator.next()); | ||
| 87 | * : | ||
| 88 | * } | ||
| 89 | * </pre> | ||
| 90 | * </blockquote> | ||
| 91 | * | ||
| 92 | * <p> | ||
| 93 | * <code>CollationElementIterator.next</code> returns the collation order | ||
| 94 | * of the next character. A collation order consists of primary order, | ||
| 95 | * secondary order and tertiary order. The data type of the collation | ||
| 96 | * order is <strong>int</strong>. The first 16 bits of a collation order | ||
| 97 | * is its primary order; the next 8 bits is the secondary order and the | ||
| 98 | * last 8 bits is the tertiary order. | ||
| 99 | * | ||
| 100 | * <p><b>Note:</b> <code>CollationElementIterator</code> is a part of | ||
| 101 | * <code>RuleBasedCollator</code> implementation. It is only usable | ||
| 102 | * with <code>RuleBasedCollator</code> instances. | ||
| 103 | * | ||
| 104 | * @see Collator | ||
| 105 | * @see RuleBasedCollator | ||
| 106 | * @author Helena Shih, Laura Werner, Richard Gillam | ||
| 107 | */ | ||
| 108 | public final class CollationElementIterator | ||
| 109 | { | ||
| 110 | /** | ||
| 111 | * Null order which indicates the end of string is reached by the | ||
| 112 | * cursor. | ||
| 113 | */ | ||
| 114 | public final static int NULLORDER = 0xffffffff; | ||
| 115 | |||
| 116 | /** | ||
| 117 | * CollationElementIterator constructor. This takes the source string and | ||
| 118 | * the collation object. The cursor will walk thru the source string based | ||
| 119 | * on the predefined collation rules. If the source string is empty, | ||
| 120 | * NULLORDER will be returned on the calls to next(). | ||
| 121 | * @param sourceText the source string. | ||
| 122 | * @param order the collation object. | ||
| 123 | */ | ||
| 124 | CollationElementIterator(String sourceText, RuleBasedCollator owner) { | ||
| 125 | this.owner = owner; | ||
| 126 | ordering = owner.getTables(); | ||
| 127 | if ( sourceText.length() != 0 ) { | ||
| 128 | NormalizerBase.Mode mode = | ||
| 129 | CollatorUtilities.toNormalizerMode(owner.getDecomposition()); | ||
| 130 | text = new NormalizerBase(sourceText, mode); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | /** | ||
| 135 | * CollationElementIterator constructor. This takes the source string and | ||
| 136 | * the collation object. The cursor will walk thru the source string based | ||
| 137 | * on the predefined collation rules. If the source string is empty, | ||
| 138 | * NULLORDER will be returned on the calls to next(). | ||
| 139 | * @param sourceText the source string. | ||
| 140 | * @param order the collation object. | ||
| 141 | */ | ||
| 142 | CollationElementIterator(CharacterIterator sourceText, RuleBasedCollator owner) { | ||
| 143 | this.owner = owner; | ||
| 144 | ordering = owner.getTables(); | ||
| 145 | NormalizerBase.Mode mode = | ||
| 146 | CollatorUtilities.toNormalizerMode(owner.getDecomposition()); | ||
| 147 | text = new NormalizerBase(sourceText, mode); | ||
| 148 | } | ||
| 149 | |||
| 150 | /** | ||
| 151 | * Resets the cursor to the beginning of the string. The next call | ||
| 152 | * to next() will return the first collation element in the string. | ||
| 153 | */ | ||
| 154 | public void reset() | ||
| 155 | { | ||
| 156 | if (text != null) { | ||
| 157 | text.reset(); | ||
| 158 | NormalizerBase.Mode mode = | ||
| 159 | CollatorUtilities.toNormalizerMode(owner.getDecomposition()); | ||
| 160 | text.setMode(mode); | ||
| 161 | } | ||
| 162 | buffer = null; | ||
| 163 | expIndex = 0; | ||
| 164 | swapOrder = 0; | ||
| 165 | } | ||
| 166 | |||
| 167 | /** | ||
| 168 | * Get the next collation element in the string. <p>This iterator iterates | ||
| 169 | * over a sequence of collation elements that were built from the string. | ||
| 170 | * Because there isn't necessarily a one-to-one mapping from characters to | ||
| 171 | * collation elements, this doesn't mean the same thing as "return the | ||
| 172 | * collation element [or ordering priority] of the next character in the | ||
| 173 | * string".</p> | ||
| 174 | * <p>This function returns the collation element that the iterator is currently | ||
| 175 | * pointing to and then updates the internal pointer to point to the next element. | ||
| 176 | * previous() updates the pointer first and then returns the element. This | ||
| 177 | * means that when you change direction while iterating (i.e., call next() and | ||
| 178 | * then call previous(), or call previous() and then call next()), you'll get | ||
| 179 | * back the same element twice.</p> | ||
| 180 | */ | ||
| 181 | public int next() | ||
| 182 | { | ||
| 183 | if (text == null) { | ||
| 184 | return NULLORDER; | ||
| 185 | } | ||
| 186 | NormalizerBase.Mode textMode = text.getMode(); | ||
| 187 | // convert the owner's mode to something the Normalizer understands | ||
| 188 | NormalizerBase.Mode ownerMode = | ||
| 189 | CollatorUtilities.toNormalizerMode(owner.getDecomposition()); | ||
| 190 | if (textMode != ownerMode) { | ||
| 191 | text.setMode(ownerMode); | ||
| 192 | } | ||
| 193 | |||
| 194 | // if buffer contains any decomposed char values | ||
| 195 | // return their strength orders before continuing in | ||
| 196 | // the Normalizer's CharacterIterator. | ||
| 197 | if (buffer != null) { | ||
| 198 | if (expIndex < buffer.length) { | ||
| 199 | return strengthOrder(buffer[expIndex++]); | ||
| 200 | } else { | ||
| 201 | buffer = null; | ||
| 202 | expIndex = 0; | ||
| 203 | } | ||
| 204 | } else if (swapOrder != 0) { | ||
| 205 | if (Character.isSupplementaryCodePoint(swapOrder)) { | ||
| 206 | char[] chars = Character.toChars(swapOrder); | ||
| 207 | swapOrder = chars[1]; | ||
| 208 | return chars[0] << 16; | ||
| 209 | } | ||
| 210 | int order = swapOrder << 16; | ||
| 211 | swapOrder = 0; | ||
| 212 | return order; | ||
| 213 | } | ||
| 214 | int ch = text.next(); | ||
| 215 | |||
| 216 | // are we at the end of Normalizer's text? | ||
| 217 | if (ch == NormalizerBase.DONE) { | ||
| 218 | return NULLORDER; | ||
| 219 | } | ||
| 220 | |||
| 221 | int value = ordering.getUnicodeOrder(ch); | ||
| 222 | if (value == RuleBasedCollator.UNMAPPED) { | ||
| 223 | swapOrder = ch; | ||
| 224 | return UNMAPPEDCHARVALUE; | ||
| 225 | } | ||
| 226 | else if (value >= RuleBasedCollator.CONTRACTCHARINDEX) { | ||
| 227 | value = nextContractChar(ch); | ||
| 228 | } | ||
| 229 | if (value >= RuleBasedCollator.EXPANDCHARINDEX) { | ||
| 230 | buffer = ordering.getExpandValueList(value); | ||
| 231 | expIndex = 0; | ||
| 232 | value = buffer[expIndex++]; | ||
| 233 | } | ||
| 234 | |||
| 235 | if (ordering.isSEAsianSwapping()) { | ||
| 236 | int consonant; | ||
| 237 | if (isThaiPreVowel(ch)) { | ||
| 238 | consonant = text.next(); | ||
| 239 | if (isThaiBaseConsonant(consonant)) { | ||
| 240 | buffer = makeReorderedBuffer(consonant, value, buffer, true); | ||
| 241 | value = buffer[0]; | ||
| 242 | expIndex = 1; | ||
| 243 | } else if (consonant != NormalizerBase.DONE) { | ||
| 244 | text.previous(); | ||
| 245 | } | ||
| 246 | } | ||
| 247 | if (isLaoPreVowel(ch)) { | ||
| 248 | consonant = text.next(); | ||
| 249 | if (isLaoBaseConsonant(consonant)) { | ||
| 250 | buffer = makeReorderedBuffer(consonant, value, buffer, true); | ||
| 251 | value = buffer[0]; | ||
| 252 | expIndex = 1; | ||
| 253 | } else if (consonant != NormalizerBase.DONE) { | ||
| 254 | text.previous(); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | return strengthOrder(value); | ||
| 260 | } | ||
| 261 | |||
| 262 | /** | ||
| 263 | * Get the previous collation element in the string. <p>This iterator iterates | ||
| 264 | * over a sequence of collation elements that were built from the string. | ||
| 265 | * Because there isn't necessarily a one-to-one mapping from characters to | ||
| 266 | * collation elements, this doesn't mean the same thing as "return the | ||
| 267 | * collation element [or ordering priority] of the previous character in the | ||
| 268 | * string".</p> | ||
| 269 | * <p>This function updates the iterator's internal pointer to point to the | ||
| 270 | * collation element preceding the one it's currently pointing to and then | ||
| 271 | * returns that element, while next() returns the current element and then | ||
| 272 | * updates the pointer. This means that when you change direction while | ||
| 273 | * iterating (i.e., call next() and then call previous(), or call previous() | ||
| 274 | * and then call next()), you'll get back the same element twice.</p> | ||
| 275 | * @since 1.2 | ||
| 276 | */ | ||
| 277 | public int previous() | ||
| 278 | { | ||
| 279 | if (text == null) { | ||
| 280 | return NULLORDER; | ||
| 281 | } | ||
| 282 | NormalizerBase.Mode textMode = text.getMode(); | ||
| 283 | // convert the owner's mode to something the Normalizer understands | ||
| 284 | NormalizerBase.Mode ownerMode = | ||
| 285 | CollatorUtilities.toNormalizerMode(owner.getDecomposition()); | ||
| 286 | if (textMode != ownerMode) { | ||
| 287 | text.setMode(ownerMode); | ||
| 288 | } | ||
| 289 | if (buffer != null) { | ||
| 290 | if (expIndex > 0) { | ||
| 291 | return strengthOrder(buffer[--expIndex]); | ||
| 292 | } else { | ||
| 293 | buffer = null; | ||
| 294 | expIndex = 0; | ||
| 295 | } | ||
| 296 | } else if (swapOrder != 0) { | ||
| 297 | if (Character.isSupplementaryCodePoint(swapOrder)) { | ||
| 298 | char[] chars = Character.toChars(swapOrder); | ||
| 299 | swapOrder = chars[1]; | ||
| 300 | return chars[0] << 16; | ||
| 301 | } | ||
| 302 | int order = swapOrder << 16; | ||
| 303 | swapOrder = 0; | ||
| 304 | return order; | ||
| 305 | } | ||
| 306 | int ch = text.previous(); | ||
| 307 | if (ch == NormalizerBase.DONE) { | ||
| 308 | return NULLORDER; | ||
| 309 | } | ||
| 310 | |||
| 311 | int value = ordering.getUnicodeOrder(ch); | ||
| 312 | |||
| 313 | if (value == RuleBasedCollator.UNMAPPED) { | ||
| 314 | swapOrder = UNMAPPEDCHARVALUE; | ||
| 315 | return ch; | ||
| 316 | } else if (value >= RuleBasedCollator.CONTRACTCHARINDEX) { | ||
| 317 | value = prevContractChar(ch); | ||
| 318 | } | ||
| 319 | if (value >= RuleBasedCollator.EXPANDCHARINDEX) { | ||
| 320 | buffer = ordering.getExpandValueList(value); | ||
| 321 | expIndex = buffer.length; | ||
| 322 | value = buffer[--expIndex]; | ||
| 323 | } | ||
| 324 | |||
| 325 | if (ordering.isSEAsianSwapping()) { | ||
| 326 | int vowel; | ||
| 327 | if (isThaiBaseConsonant(ch)) { | ||
| 328 | vowel = text.previous(); | ||
| 329 | if (isThaiPreVowel(vowel)) { | ||
| 330 | buffer = makeReorderedBuffer(vowel, value, buffer, false); | ||
| 331 | expIndex = buffer.length - 1; | ||
| 332 | value = buffer[expIndex]; | ||
| 333 | } else { | ||
| 334 | text.next(); | ||
| 335 | } | ||
| 336 | } | ||
| 337 | if (isLaoBaseConsonant(ch)) { | ||
| 338 | vowel = text.previous(); | ||
| 339 | if (isLaoPreVowel(vowel)) { | ||
| 340 | buffer = makeReorderedBuffer(vowel, value, buffer, false); | ||
| 341 | expIndex = buffer.length - 1; | ||
| 342 | value = buffer[expIndex]; | ||
| 343 | } else { | ||
| 344 | text.next(); | ||
| 345 | } | ||
| 346 | } | ||
| 347 | } | ||
| 348 | |||
| 349 | return strengthOrder(value); | ||
| 350 | } | ||
| 351 | |||
| 352 | /** | ||
| 353 | * Return the primary component of a collation element. | ||
| 354 | * @param order the collation element | ||
| 355 | * @return the element's primary component | ||
| 356 | */ | ||
| 357 | public final static int primaryOrder(int order) | ||
| 358 | { | ||
| 359 | order &= RBCollationTables.PRIMARYORDERMASK; | ||
| 360 | return (order >>> RBCollationTables.PRIMARYORDERSHIFT); | ||
| 361 | } | ||
| 362 | /** | ||
| 363 | * Return the secondary component of a collation element. | ||
| 364 | * @param order the collation element | ||
| 365 | * @return the element's secondary component | ||
| 366 | */ | ||
| 367 | public final static short secondaryOrder(int order) | ||
| 368 | { | ||
| 369 | order = order & RBCollationTables.SECONDARYORDERMASK; | ||
| 370 | return ((short)(order >> RBCollationTables.SECONDARYORDERSHIFT)); | ||
| 371 | } | ||
| 372 | /** | ||
| 373 | * Return the tertiary component of a collation element. | ||
| 374 | * @param order the collation element | ||
| 375 | * @return the element's tertiary component | ||
| 376 | */ | ||
| 377 | public final static short tertiaryOrder(int order) | ||
| 378 | { | ||
| 379 | return ((short)(order &= RBCollationTables.TERTIARYORDERMASK)); | ||
| 380 | } | ||
| 381 | |||
| 382 | /** | ||
| 383 | * Get the comparison order in the desired strength. Ignore the other | ||
| 384 | * differences. | ||
| 385 | * @param order The order value | ||
| 386 | */ | ||
| 387 | final int strengthOrder(int order) | ||
| 388 | { | ||
| 389 | int s = owner.getStrength(); | ||
| 390 | if (s == Collator.PRIMARY) | ||
| 391 | { | ||
| 392 | order &= RBCollationTables.PRIMARYDIFFERENCEONLY; | ||
| 393 | } else if (s == Collator.SECONDARY) | ||
| 394 | { | ||
| 395 | order &= RBCollationTables.SECONDARYDIFFERENCEONLY; | ||
| 396 | } | ||
| 397 | return order; | ||
| 398 | } | ||
| 399 | |||
| 400 | /** | ||
| 401 | * Sets the iterator to point to the collation element corresponding to | ||
| 402 | * the specified character (the parameter is a CHARACTER offset in the | ||
| 403 | * original string, not an offset into its corresponding sequence of | ||
| 404 | * collation elements). The value returned by the next call to next() | ||
| 405 | * will be the collation element corresponding to the specified position | ||
| 406 | * in the text. If that position is in the middle of a contracting | ||
| 407 | * character sequence, the result of the next call to next() is the | ||
| 408 | * collation element for that sequence. This means that getOffset() | ||
| 409 | * is not guaranteed to return the same value as was passed to a preceding | ||
| 410 | * call to setOffset(). | ||
| 411 | * | ||
| 412 | * @param newOffset The new character offset into the original text. | ||
| 413 | * @since 1.2 | ||
| 414 | */ | ||
| 415 | public void setOffset(int newOffset) | ||
| 416 | { | ||
| 417 | if (text != null) { | ||
| 418 | if (newOffset < text.getBeginIndex() | ||
| 419 | || newOffset >= text.getEndIndex()) { | ||
| 420 | text.setIndexOnly(newOffset); | ||
| 421 | } else { | ||
| 422 | int c = text.setIndex(newOffset); | ||
| 423 | |||
| 424 | // if the desired character isn't used in a contracting character | ||
| 425 | // sequence, bypass all the backing-up logic-- we're sitting on | ||
| 426 | // the right character already | ||
| 427 | if (ordering.usedInContractSeq(c)) { | ||
| 428 | // walk backwards through the string until we see a character | ||
| 429 | // that DOESN'T participate in a contracting character sequence | ||
| 430 | while (ordering.usedInContractSeq(c)) { | ||
| 431 | c = text.previous(); | ||
| 432 | } | ||
| 433 | // now walk forward using this object's next() method until | ||
| 434 | // we pass the starting point and set our current position | ||
| 435 | // to the beginning of the last "character" before or at | ||
| 436 | // our starting position | ||
| 437 | int last = text.getIndex(); | ||
| 438 | while (text.getIndex() <= newOffset) { | ||
| 439 | last = text.getIndex(); | ||
| 440 | next(); | ||
| 441 | } | ||
| 442 | text.setIndexOnly(last); | ||
| 443 | // we don't need this, since last is the last index | ||
| 444 | // that is the starting of the contraction which encompass | ||
| 445 | // newOffset | ||
| 446 | // text.previous(); | ||
| 447 | } | ||
| 448 | } | ||
| 449 | } | ||
| 450 | buffer = null; | ||
| 451 | expIndex = 0; | ||
| 452 | swapOrder = 0; | ||
| 453 | } | ||
| 454 | |||
| 455 | /** | ||
| 456 | * Returns the character offset in the original text corresponding to the next | ||
| 457 | * collation element. (That is, getOffset() returns the position in the text | ||
| 458 | * corresponding to the collation element that will be returned by the next | ||
| 459 | * call to next().) This value will always be the index of the FIRST character | ||
| 460 | * corresponding to the collation element (a contracting character sequence is | ||
| 461 | * when two or more characters all correspond to the same collation element). | ||
| 462 | * This means if you do setOffset(x) followed immediately by getOffset(), getOffset() | ||
| 463 | * won't necessarily return x. | ||
| 464 | * | ||
| 465 | * @return The character offset in the original text corresponding to the collation | ||
| 466 | * element that will be returned by the next call to next(). | ||
| 467 | * @since 1.2 | ||
| 468 | */ | ||
| 469 | public int getOffset() | ||
| 470 | { | ||
| 471 | return (text != null) ? text.getIndex() : 0; | ||
| 472 | } | ||
| 473 | |||
| 474 | |||
| 475 | /** | ||
| 476 | * Return the maximum length of any expansion sequences that end | ||
| 477 | * with the specified comparison order. | ||
| 478 | * @param order a collation order returned by previous or next. | ||
| 479 | * @return the maximum length of any expansion sequences ending | ||
| 480 | * with the specified order. | ||
| 481 | * @since 1.2 | ||
| 482 | */ | ||
| 483 | public int getMaxExpansion(int order) | ||
| 484 | { | ||
| 485 | return ordering.getMaxExpansion(order); | ||
| 486 | } | ||
| 487 | |||
| 488 | /** | ||
| 489 | * Set a new string over which to iterate. | ||
| 490 | * | ||
| 491 | * @param source the new source text | ||
| 492 | * @since 1.2 | ||
| 493 | */ | ||
| 494 | public void setText(String source) | ||
| 495 | { | ||
| 496 | buffer = null; | ||
| 497 | swapOrder = 0; | ||
| 498 | expIndex = 0; | ||
| 499 | NormalizerBase.Mode mode = | ||
| 500 | CollatorUtilities.toNormalizerMode(owner.getDecomposition()); | ||
| 501 | if (text == null) { | ||
| 502 | text = new NormalizerBase(source, mode); | ||
| 503 | } else { | ||
| 504 | text.setMode(mode); | ||
| 505 | text.setText(source); | ||
| 506 | } | ||
| 507 | } | ||
| 508 | |||
| 509 | /** | ||
| 510 | * Set a new string over which to iterate. | ||
| 511 | * | ||
| 512 | * @param source the new source text. | ||
| 513 | * @since 1.2 | ||
| 514 | */ | ||
| 515 | public void setText(CharacterIterator source) | ||
| 516 | { | ||
| 517 | buffer = null; | ||
| 518 | swapOrder = 0; | ||
| 519 | expIndex = 0; | ||
| 520 | NormalizerBase.Mode mode = | ||
| 521 | CollatorUtilities.toNormalizerMode(owner.getDecomposition()); | ||
| 522 | if (text == null) { | ||
| 523 | text = new NormalizerBase(source, mode); | ||
| 524 | } else { | ||
| 525 | text.setMode(mode); | ||
| 526 | text.setText(source); | ||
| 527 | } | ||
| 528 | } | ||
| 529 | |||
| 530 | //============================================================ | ||
| 531 | // privates | ||
| 532 | //============================================================ | ||
| 533 | |||
| 534 | /** | ||
| 535 | * Determine if a character is a Thai vowel (which sorts after | ||
| 536 | * its base consonant). | ||
| 537 | */ | ||
| 538 | private final static boolean isThaiPreVowel(int ch) { | ||
| 539 | return (ch >= 0x0e40) && (ch <= 0x0e44); | ||
| 540 | } | ||
| 541 | |||
| 542 | /** | ||
| 543 | * Determine if a character is a Thai base consonant | ||
| 544 | */ | ||
| 545 | private final static boolean isThaiBaseConsonant(int ch) { | ||
| 546 | return (ch >= 0x0e01) && (ch <= 0x0e2e); | ||
| 547 | } | ||
| 548 | |||
| 549 | /** | ||
| 550 | * Determine if a character is a Lao vowel (which sorts after | ||
| 551 | * its base consonant). | ||
| 552 | */ | ||
| 553 | private final static boolean isLaoPreVowel(int ch) { | ||
| 554 | return (ch >= 0x0ec0) && (ch <= 0x0ec4); | ||
| 555 | } | ||
| 556 | |||
| 557 | /** | ||
| 558 | * Determine if a character is a Lao base consonant | ||
| 559 | */ | ||
| 560 | private final static boolean isLaoBaseConsonant(int ch) { | ||
| 561 | return (ch >= 0x0e81) && (ch <= 0x0eae); | ||
| 562 | } | ||
| 563 | |||
| 564 | /** | ||
| 565 | * This method produces a buffer which contains the collation | ||
| 566 | * elements for the two characters, with colFirst's values preceding | ||
| 567 | * another character's. Presumably, the other character precedes colFirst | ||
| 568 | * in logical order (otherwise you wouldn't need this method would you?). | ||
| 569 | * The assumption is that the other char's value(s) have already been | ||
| 570 | * computed. If this char has a single element it is passed to this | ||
| 571 | * method as lastValue, and lastExpansion is null. If it has an | ||
| 572 | * expansion it is passed in lastExpansion, and colLastValue is ignored. | ||
| 573 | */ | ||
| 574 | private int[] makeReorderedBuffer(int colFirst, | ||
| 575 | int lastValue, | ||
| 576 | int[] lastExpansion, | ||
| 577 | boolean forward) { | ||
| 578 | |||
| 579 | int[] result; | ||
| 580 | |||
| 581 | int firstValue = ordering.getUnicodeOrder(colFirst); | ||
| 582 | if (firstValue >= RuleBasedCollator.CONTRACTCHARINDEX) { | ||
| 583 | firstValue = forward? nextContractChar(colFirst) : prevContractChar(colFirst); | ||
| 584 | } | ||
| 585 | |||
| 586 | int[] firstExpansion = null; | ||
| 587 | if (firstValue >= RuleBasedCollator.EXPANDCHARINDEX) { | ||
| 588 | firstExpansion = ordering.getExpandValueList(firstValue); | ||
| 589 | } | ||
| 590 | |||
| 591 | if (!forward) { | ||
| 592 | int temp1 = firstValue; | ||
| 593 | firstValue = lastValue; | ||
| 594 | lastValue = temp1; | ||
| 595 | int[] temp2 = firstExpansion; | ||
| 596 | firstExpansion = lastExpansion; | ||
| 597 | lastExpansion = temp2; | ||
| 598 | } | ||
| 599 | |||
| 600 | if (firstExpansion == null && lastExpansion == null) { | ||
| 601 | result = new int [2]; | ||
| 602 | result[0] = firstValue; | ||
| 603 | result[1] = lastValue; | ||
| 604 | } | ||
| 605 | else { | ||
| 606 | int firstLength = firstExpansion==null? 1 : firstExpansion.length; | ||
| 607 | int lastLength = lastExpansion==null? 1 : lastExpansion.length; | ||
| 608 | result = new int[firstLength + lastLength]; | ||
| 609 | |||
| 610 | if (firstExpansion == null) { | ||
| 611 | result[0] = firstValue; | ||
| 612 | } | ||
| 613 | else { | ||
| 614 | System.arraycopy(firstExpansion, 0, result, 0, firstLength); | ||
| 615 | } | ||
| 616 | |||
| 617 | if (lastExpansion == null) { | ||
| 618 | result[firstLength] = lastValue; | ||
| 619 | } | ||
| 620 | else { | ||
| 621 | System.arraycopy(lastExpansion, 0, result, firstLength, lastLength); | ||
| 622 | } | ||
| 623 | } | ||
| 624 | |||
| 625 | return result; | ||
| 626 | } | ||
| 627 | |||
| 628 | /** | ||
| 629 | * Check if a comparison order is ignorable. | ||
| 630 | * @return true if a character is ignorable, false otherwise. | ||
| 631 | */ | ||
| 632 | final static boolean isIgnorable(int order) | ||
| 633 | { | ||
| 634 | return ((primaryOrder(order) == 0) ? true : false); | ||
| 635 | } | ||
| 636 | |||
| 637 | /** | ||
| 638 | * Get the ordering priority of the next contracting character in the | ||
| 639 | * string. | ||
| 640 | * @param ch the starting character of a contracting character token | ||
| 641 | * @return the next contracting character's ordering. Returns NULLORDER | ||
| 642 | * if the end of string is reached. | ||
| 643 | */ | ||
| 644 | private int nextContractChar(int ch) | ||
| 645 | { | ||
| 646 | // First get the ordering of this single character, | ||
| 647 | // which is always the first element in the list | ||
| 648 | Vector list = ordering.getContractValues(ch); | ||
| 649 | EntryPair pair = (EntryPair)list.firstElement(); | ||
| 650 | int order = pair.value; | ||
| 651 | |||
| 652 | // find out the length of the longest contracting character sequence in the list. | ||
| 653 | // There's logic in the builder code to make sure the longest sequence is always | ||
| 654 | // the last. | ||
| 655 | pair = (EntryPair)list.lastElement(); | ||
| 656 | int maxLength = pair.entryName.length(); | ||
| 657 | |||
| 658 | // (the Normalizer is cloned here so that the seeking we do in the next loop | ||
| 659 | // won't affect our real position in the text) | ||
| 660 | NormalizerBase tempText = (NormalizerBase)text.clone(); | ||
| 661 | |||
| 662 | // extract the next maxLength characters in the string (we have to do this using the | ||
| 663 | // Normalizer to ensure that our offsets correspond to those the rest of the | ||
| 664 | // iterator is using) and store it in "fragment". | ||
| 665 | tempText.previous(); | ||
| 666 | key.setLength(0); | ||
| 667 | int c = tempText.next(); | ||
| 668 | while (maxLength > 0 && c != NormalizerBase.DONE) { | ||
| 669 | if (Character.isSupplementaryCodePoint(c)) { | ||
| 670 | key.append(Character.toChars(c)); | ||
| 671 | maxLength -= 2; | ||
| 672 | } else { | ||
| 673 | key.append((char)c); | ||
| 674 | --maxLength; | ||
| 675 | } | ||
| 676 | c = tempText.next(); | ||
| 677 | } | ||
| 678 | String fragment = key.toString(); | ||
| 679 | // now that we have that fragment, iterate through this list looking for the | ||
| 680 | // longest sequence that matches the characters in the actual text. (maxLength | ||
| 681 | // is used here to keep track of the length of the longest sequence) | ||
| 682 | // Upon exit from this loop, maxLength will contain the length of the matching | ||
| 683 | // sequence and order will contain the collation-element value corresponding | ||
| 684 | // to this sequence | ||
| 685 | maxLength = 1; | ||
| 686 | for (int i = list.size() - 1; i > 0; i--) { | ||
| 687 | pair = (EntryPair)list.elementAt(i); | ||
| 688 | if (!pair.fwd) | ||
| 689 | continue; | ||
| 690 | |||
| 691 | if (fragment.startsWith(pair.entryName) && pair.entryName.length() | ||
| 692 | > maxLength) { | ||
| 693 | maxLength = pair.entryName.length(); | ||
| 694 | order = pair.value; | ||
| 695 | } | ||
| 696 | } | ||
| 697 | |||
| 698 | // seek our current iteration position to the end of the matching sequence | ||
| 699 | // and return the appropriate collation-element value (if there was no matching | ||
| 700 | // sequence, we're already seeked to the right position and order already contains | ||
| 701 | // the correct collation-element value for the single character) | ||
| 702 | while (maxLength > 1) { | ||
| 703 | c = text.next(); | ||
| 704 | maxLength -= Character.charCount(c); | ||
| 705 | } | ||
| 706 | return order; | ||
| 707 | } | ||
| 708 | |||
| 709 | /** | ||
| 710 | * Get the ordering priority of the previous contracting character in the | ||
| 711 | * string. | ||
| 712 | * @param ch the starting character of a contracting character token | ||
| 713 | * @return the next contracting character's ordering. Returns NULLORDER | ||
| 714 | * if the end of string is reached. | ||
| 715 | */ | ||
| 716 | private int prevContractChar(int ch) | ||
| 717 | { | ||
| 718 | // This function is identical to nextContractChar(), except that we've | ||
| 719 | // switched things so that the next() and previous() calls on the Normalizer | ||
| 720 | // are switched and so that we skip entry pairs with the fwd flag turned on | ||
| 721 | // rather than off. Notice that we still use append() and startsWith() when | ||
| 722 | // working on the fragment. This is because the entry pairs that are used | ||
| 723 | // in reverse iteration have their names reversed already. | ||
| 724 | Vector list = ordering.getContractValues(ch); | ||
| 725 | EntryPair pair = (EntryPair)list.firstElement(); | ||
| 726 | int order = pair.value; | ||
| 727 | |||
| 728 | pair = (EntryPair)list.lastElement(); | ||
| 729 | int maxLength = pair.entryName.length(); | ||
| 730 | |||
| 731 | NormalizerBase tempText = (NormalizerBase)text.clone(); | ||
| 732 | |||
| 733 | tempText.next(); | ||
| 734 | key.setLength(0); | ||
| 735 | int c = tempText.previous(); | ||
| 736 | while (maxLength > 0 && c != NormalizerBase.DONE) { | ||
| 737 | if (Character.isSupplementaryCodePoint(c)) { | ||
| 738 | key.append(Character.toChars(c)); | ||
| 739 | maxLength -= 2; | ||
| 740 | } else { | ||
| 741 | key.append((char)c); | ||
| 742 | --maxLength; | ||
| 743 | } | ||
| 744 | c = tempText.previous(); | ||
| 745 | } | ||
| 746 | String fragment = key.toString(); | ||
| 747 | |||
| 748 | maxLength = 1; | ||
| 749 | for (int i = list.size() - 1; i > 0; i--) { | ||
| 750 | pair = (EntryPair)list.elementAt(i); | ||
| 751 | if (pair.fwd) | ||
| 752 | continue; | ||
| 753 | |||
| 754 | if (fragment.startsWith(pair.entryName) && pair.entryName.length() | ||
| 755 | > maxLength) { | ||
| 756 | maxLength = pair.entryName.length(); | ||
| 757 | order = pair.value; | ||
| 758 | } | ||
| 759 | } | ||
| 760 | |||
| 761 | while (maxLength > 1) { | ||
| 762 | c = text.previous(); | ||
| 763 | maxLength -= Character.charCount(c); | ||
| 764 | } | ||
| 765 | return order; | ||
| 766 | } | ||
| 767 | |||
| 768 | final static int UNMAPPEDCHARVALUE = 0x7FFF0000; | ||
| 769 | |||
| 770 | private NormalizerBase text = null; | ||
| 771 | private int[] buffer = null; | ||
| 772 | private int expIndex = 0; | ||
| 773 | private StringBuffer key = new StringBuffer(5); | ||
| 774 | private int swapOrder = 0; | ||
| 775 | private RBCollationTables ordering; | ||
| 776 | private RuleBasedCollator owner; | ||
| 777 | } | ||
Binary file not shown.
| @@ -0,0 +1,145 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1997, 2005, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | /** | ||
| 42 | * A <code>CollationKey</code> represents a <code>String</code> under the | ||
| 43 | * rules of a specific <code>Collator</code> object. Comparing two | ||
| 44 | * <code>CollationKey</code>s returns the relative order of the | ||
| 45 | * <code>String</code>s they represent. Using <code>CollationKey</code>s | ||
| 46 | * to compare <code>String</code>s is generally faster than using | ||
| 47 | * <code>Collator.compare</code>. Thus, when the <code>String</code>s | ||
| 48 | * must be compared multiple times, for example when sorting a list | ||
| 49 | * of <code>String</code>s. It's more efficient to use <code>CollationKey</code>s. | ||
| 50 | * | ||
| 51 | * <p> | ||
| 52 | * You can not create <code>CollationKey</code>s directly. Rather, | ||
| 53 | * generate them by calling <code>Collator.getCollationKey</code>. | ||
| 54 | * You can only compare <code>CollationKey</code>s generated from | ||
| 55 | * the same <code>Collator</code> object. | ||
| 56 | * | ||
| 57 | * <p> | ||
| 58 | * Generating a <code>CollationKey</code> for a <code>String</code> | ||
| 59 | * involves examining the entire <code>String</code> | ||
| 60 | * and converting it to series of bits that can be compared bitwise. This | ||
| 61 | * allows fast comparisons once the keys are generated. The cost of generating | ||
| 62 | * keys is recouped in faster comparisons when <code>String</code>s need | ||
| 63 | * to be compared many times. On the other hand, the result of a comparison | ||
| 64 | * is often determined by the first couple of characters of each <code>String</code>. | ||
| 65 | * <code>Collator.compare</code> examines only as many characters as it needs which | ||
| 66 | * allows it to be faster when doing single comparisons. | ||
| 67 | * <p> | ||
| 68 | * The following example shows how <code>CollationKey</code>s might be used | ||
| 69 | * to sort a list of <code>String</code>s. | ||
| 70 | * <blockquote> | ||
| 71 | * <pre> | ||
| 72 | * // Create an array of CollationKeys for the Strings to be sorted. | ||
| 73 | * Collator myCollator = Collator.getInstance(); | ||
| 74 | * CollationKey[] keys = new CollationKey[3]; | ||
| 75 | * keys[0] = myCollator.getCollationKey("Tom"); | ||
| 76 | * keys[1] = myCollator.getCollationKey("Dick"); | ||
| 77 | * keys[2] = myCollator.getCollationKey("Harry"); | ||
| 78 | * sort( keys ); | ||
| 79 | * <br> | ||
| 80 | * //... | ||
| 81 | * <br> | ||
| 82 | * // Inside body of sort routine, compare keys this way | ||
| 83 | * if( keys[i].compareTo( keys[j] ) > 0 ) | ||
| 84 | * // swap keys[i] and keys[j] | ||
| 85 | * <br> | ||
| 86 | * //... | ||
| 87 | * <br> | ||
| 88 | * // Finally, when we've returned from sort. | ||
| 89 | * System.out.println( keys[0].getSourceString() ); | ||
| 90 | * System.out.println( keys[1].getSourceString() ); | ||
| 91 | * System.out.println( keys[2].getSourceString() ); | ||
| 92 | * </pre> | ||
| 93 | * </blockquote> | ||
| 94 | * | ||
| 95 | * @see Collator | ||
| 96 | * @see RuleBasedCollator | ||
| 97 | * @author Helena Shih | ||
| 98 | */ | ||
| 99 | |||
| 100 | public abstract class CollationKey implements Comparable<CollationKey> { | ||
| 101 | /** | ||
| 102 | * Compare this CollationKey to the target CollationKey. The collation rules of the | ||
| 103 | * Collator object which created these keys are applied. <strong>Note:</strong> | ||
| 104 | * CollationKeys created by different Collators can not be compared. | ||
| 105 | * @param target target CollationKey | ||
| 106 | * @return Returns an integer value. Value is less than zero if this is less | ||
| 107 | * than target, value is zero if this and target are equal and value is greater than | ||
| 108 | * zero if this is greater than target. | ||
| 109 | * @see java.text.Collator#compare | ||
| 110 | */ | ||
| 111 | abstract public int compareTo(CollationKey target); | ||
| 112 | |||
| 113 | /** | ||
| 114 | * Returns the String that this CollationKey represents. | ||
| 115 | */ | ||
| 116 | public String getSourceString() { | ||
| 117 | return source; | ||
| 118 | } | ||
| 119 | |||
| 120 | |||
| 121 | /** | ||
| 122 | * Converts the CollationKey to a sequence of bits. If two CollationKeys | ||
| 123 | * could be legitimately compared, then one could compare the byte arrays | ||
| 124 | * for each of those keys to obtain the same result. Byte arrays are | ||
| 125 | * organized most significant byte first. | ||
| 126 | */ | ||
| 127 | abstract public byte[] toByteArray(); | ||
| 128 | |||
| 129 | |||
| 130 | /** | ||
| 131 | * CollationKey constructor. | ||
| 132 | * | ||
| 133 | * @param source - the source string. | ||
| 134 | * @exception NullPointerException if <code>source</code> is null. | ||
| 135 | * @since 1.6 | ||
| 136 | */ | ||
| 137 | protected CollationKey(String source) { | ||
| 138 | if (source==null){ | ||
| 139 | throw new NullPointerException(); | ||
| 140 | } | ||
| 141 | this.source = source; | ||
| 142 | } | ||
| 143 | |||
| 144 | final private String source; | ||
| 145 | } | ||
Binary file not shown.
| @@ -0,0 +1,274 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2001, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996,1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996, 1997 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | /** | ||
| 41 | * CollationRules contains the default en_US collation rules as a base | ||
| 42 | * for building other collation tables. | ||
| 43 | * <p>Note that decompositions are done before these rules are used, | ||
| 44 | * so they do not have to contain accented characters, such as A-grave. | ||
| 45 | * @see RuleBasedCollator | ||
| 46 | * @see LocaleElements | ||
| 47 | * @author Helena Shih, Mark Davis | ||
| 48 | */ | ||
| 49 | final class CollationRules { | ||
| 50 | final static String DEFAULTRULES = new String( | ||
| 51 | "" // no FRENCH accent order by default, add in French Delta | ||
| 52 | // IGNORABLES (up to first < character) | ||
| 53 | // COMPLETELY IGNORE format characters | ||
| 54 | + "='\u200B'=\u200C=\u200D=\u200E=\u200F" | ||
| 55 | // Control Characters | ||
| 56 | + "=\u0000 =\u0001 =\u0002 =\u0003 =\u0004" //null, .. eot | ||
| 57 | + "=\u0005 =\u0006 =\u0007 =\u0008 ='\u0009'" //enq, ... | ||
| 58 | + "='\u000b' =\u000e" //vt,, so | ||
| 59 | + "=\u000f ='\u0010' =\u0011 =\u0012 =\u0013" //si, dle, dc1, dc2, dc3 | ||
| 60 | + "=\u0014 =\u0015 =\u0016 =\u0017 =\u0018" //dc4, nak, syn, etb, can | ||
| 61 | + "=\u0019 =\u001a =\u001b =\u001c =\u001d" //em, sub, esc, fs, gs | ||
| 62 | + "=\u001e =\u001f =\u007f" //rs, us, del | ||
| 63 | //....then the C1 Latin 1 reserved control codes | ||
| 64 | + "=\u0080 =\u0081 =\u0082 =\u0083 =\u0084 =\u0085" | ||
| 65 | + "=\u0086 =\u0087 =\u0088 =\u0089 =\u008a =\u008b" | ||
| 66 | + "=\u008c =\u008d =\u008e =\u008f =\u0090 =\u0091" | ||
| 67 | + "=\u0092 =\u0093 =\u0094 =\u0095 =\u0096 =\u0097" | ||
| 68 | + "=\u0098 =\u0099 =\u009a =\u009b =\u009c =\u009d" | ||
| 69 | + "=\u009e =\u009f" | ||
| 70 | // IGNORE except for secondary, tertiary difference | ||
| 71 | // Spaces | ||
| 72 | + ";'\u0020';'\u00A0'" // spaces | ||
| 73 | + ";'\u2000';'\u2001';'\u2002';'\u2003';'\u2004'" // spaces | ||
| 74 | + ";'\u2005';'\u2006';'\u2007';'\u2008';'\u2009'" // spaces | ||
| 75 | + ";'\u200A';'\u3000';'\uFEFF'" // spaces | ||
| 76 | + ";'\r' ;'\t' ;'\n';'\f';'\u000b'" // whitespace | ||
| 77 | |||
| 78 | // Non-spacing accents | ||
| 79 | |||
| 80 | + ";\u0301" // non-spacing acute accent | ||
| 81 | + ";\u0300" // non-spacing grave accent | ||
| 82 | + ";\u0306" // non-spacing breve accent | ||
| 83 | + ";\u0302" // non-spacing circumflex accent | ||
| 84 | + ";\u030c" // non-spacing caron/hacek accent | ||
| 85 | + ";\u030a" // non-spacing ring above accent | ||
| 86 | + ";\u030d" // non-spacing vertical line above | ||
| 87 | + ";\u0308" // non-spacing diaeresis accent | ||
| 88 | + ";\u030b" // non-spacing double acute accent | ||
| 89 | + ";\u0303" // non-spacing tilde accent | ||
| 90 | + ";\u0307" // non-spacing dot above/overdot accent | ||
| 91 | + ";\u0304" // non-spacing macron accent | ||
| 92 | + ";\u0337" // non-spacing short slash overlay (overstruck diacritic) | ||
| 93 | + ";\u0327" // non-spacing cedilla accent | ||
| 94 | + ";\u0328" // non-spacing ogonek accent | ||
| 95 | + ";\u0323" // non-spacing dot-below/underdot accent | ||
| 96 | + ";\u0332" // non-spacing underscore/underline accent | ||
| 97 | // with the rest of the general diacritical marks in binary order | ||
| 98 | + ";\u0305" // non-spacing overscore/overline | ||
| 99 | + ";\u0309" // non-spacing hook above | ||
| 100 | + ";\u030e" // non-spacing double vertical line above | ||
| 101 | + ";\u030f" // non-spacing double grave | ||
| 102 | + ";\u0310" // non-spacing chandrabindu | ||
| 103 | + ";\u0311" // non-spacing inverted breve | ||
| 104 | + ";\u0312" // non-spacing turned comma above/cedilla above | ||
| 105 | + ";\u0313" // non-spacing comma above | ||
| 106 | + ";\u0314" // non-spacing reversed comma above | ||
| 107 | + ";\u0315" // non-spacing comma above right | ||
| 108 | + ";\u0316" // non-spacing grave below | ||
| 109 | + ";\u0317" // non-spacing acute below | ||
| 110 | + ";\u0318" // non-spacing left tack below | ||
| 111 | + ";\u0319" // non-spacing tack below | ||
| 112 | + ";\u031a" // non-spacing left angle above | ||
| 113 | + ";\u031b" // non-spacing horn | ||
| 114 | + ";\u031c" // non-spacing left half ring below | ||
| 115 | + ";\u031d" // non-spacing up tack below | ||
| 116 | + ";\u031e" // non-spacing down tack below | ||
| 117 | + ";\u031f" // non-spacing plus sign below | ||
| 118 | + ";\u0320" // non-spacing minus sign below | ||
| 119 | + ";\u0321" // non-spacing palatalized hook below | ||
| 120 | + ";\u0322" // non-spacing retroflex hook below | ||
| 121 | + ";\u0324" // non-spacing double dot below | ||
| 122 | + ";\u0325" // non-spacing ring below | ||
| 123 | + ";\u0326" // non-spacing comma below | ||
| 124 | + ";\u0329" // non-spacing vertical line below | ||
| 125 | + ";\u032a" // non-spacing bridge below | ||
| 126 | + ";\u032b" // non-spacing inverted double arch below | ||
| 127 | + ";\u032c" // non-spacing hacek below | ||
| 128 | + ";\u032d" // non-spacing circumflex below | ||
| 129 | + ";\u032e" // non-spacing breve below | ||
| 130 | + ";\u032f" // non-spacing inverted breve below | ||
| 131 | + ";\u0330" // non-spacing tilde below | ||
| 132 | + ";\u0331" // non-spacing macron below | ||
| 133 | + ";\u0333" // non-spacing double underscore | ||
| 134 | + ";\u0334" // non-spacing tilde overlay | ||
| 135 | + ";\u0335" // non-spacing short bar overlay | ||
| 136 | + ";\u0336" // non-spacing long bar overlay | ||
| 137 | + ";\u0338" // non-spacing long slash overlay | ||
| 138 | + ";\u0339" // non-spacing right half ring below | ||
| 139 | + ";\u033a" // non-spacing inverted bridge below | ||
| 140 | + ";\u033b" // non-spacing square below | ||
| 141 | + ";\u033c" // non-spacing seagull below | ||
| 142 | + ";\u033d" // non-spacing x above | ||
| 143 | + ";\u033e" // non-spacing vertical tilde | ||
| 144 | + ";\u033f" // non-spacing double overscore | ||
| 145 | //+ ";\u0340" // non-spacing grave tone mark == \u0300 | ||
| 146 | //+ ";\u0341" // non-spacing acute tone mark == \u0301 | ||
| 147 | + ";\u0342;" | ||
| 148 | //+ "\u0343;" // == \u0313 | ||
| 149 | + "\u0344;\u0345;\u0360;\u0361" // newer | ||
| 150 | + ";\u0483;\u0484;\u0485;\u0486" // Cyrillic accents | ||
| 151 | |||
| 152 | + ";\u20D0;\u20D1;\u20D2" // symbol accents | ||
| 153 | + ";\u20D3;\u20D4;\u20D5" // symbol accents | ||
| 154 | + ";\u20D6;\u20D7;\u20D8" // symbol accents | ||
| 155 | + ";\u20D9;\u20DA;\u20DB" // symbol accents | ||
| 156 | + ";\u20DC;\u20DD;\u20DE" // symbol accents | ||
| 157 | + ";\u20DF;\u20E0;\u20E1" // symbol accents | ||
| 158 | |||
| 159 | + ",'\u002D';\u00AD" // dashes | ||
| 160 | + ";\u2010;\u2011;\u2012" // dashes | ||
| 161 | + ";\u2013;\u2014;\u2015" // dashes | ||
| 162 | + ";\u2212" // dashes | ||
| 163 | |||
| 164 | // other punctuation | ||
| 165 | |||
| 166 | + "<'\u005f'" // underline/underscore (spacing) | ||
| 167 | + "<\u00af" // overline or macron (spacing) | ||
| 168 | + "<'\u002c'" // comma (spacing) | ||
| 169 | + "<'\u003b'" // semicolon | ||
| 170 | + "<'\u003a'" // colon | ||
| 171 | + "<'\u0021'" // exclamation point | ||
| 172 | + "<\u00a1" // inverted exclamation point | ||
| 173 | + "<'\u003f'" // question mark | ||
| 174 | + "<\u00bf" // inverted question mark | ||
| 175 | + "<'\u002f'" // slash | ||
| 176 | + "<'\u002e'" // period/full stop | ||
| 177 | + "<\u00b4" // acute accent (spacing) | ||
| 178 | + "<'\u0060'" // grave accent (spacing) | ||
| 179 | + "<'\u005e'" // circumflex accent (spacing) | ||
| 180 | + "<\u00a8" // diaresis/umlaut accent (spacing) | ||
| 181 | + "<'\u007e'" // tilde accent (spacing) | ||
| 182 | + "<\u00b7" // middle dot (spacing) | ||
| 183 | + "<\u00b8" // cedilla accent (spacing) | ||
| 184 | + "<'\u0027'" // apostrophe | ||
| 185 | + "<'\"'" // quotation marks | ||
| 186 | + "<\u00ab" // left angle quotes | ||
| 187 | + "<\u00bb" // right angle quotes | ||
| 188 | + "<'\u0028'" // left parenthesis | ||
| 189 | + "<'\u0029'" // right parenthesis | ||
| 190 | + "<'\u005b'" // left bracket | ||
| 191 | + "<'\u005d'" // right bracket | ||
| 192 | + "<'\u007b'" // left brace | ||
| 193 | + "<'\u007d'" // right brace | ||
| 194 | + "<\u00a7" // section symbol | ||
| 195 | + "<\u00b6" // paragraph symbol | ||
| 196 | + "<\u00a9" // copyright symbol | ||
| 197 | + "<\u00ae" // registered trademark symbol | ||
| 198 | + "<'\u0040'" // at sign | ||
| 199 | + "<\u00a4" // international currency symbol | ||
| 200 | + "<\u0e3f" // baht sign | ||
| 201 | + "<\u00a2" // cent sign | ||
| 202 | + "<\u20a1" // colon sign | ||
| 203 | + "<\u20a2" // cruzeiro sign | ||
| 204 | + "<'\u0024'" // dollar sign | ||
| 205 | + "<\u20ab" // dong sign | ||
| 206 | + "<\u20ac" // euro sign | ||
| 207 | + "<\u20a3" // franc sign | ||
| 208 | + "<\u20a4" // lira sign | ||
| 209 | + "<\u20a5" // mill sign | ||
| 210 | + "<\u20a6" // naira sign | ||
| 211 | + "<\u20a7" // peseta sign | ||
| 212 | + "<\u00a3" // pound-sterling sign | ||
| 213 | + "<\u20a8" // rupee sign | ||
| 214 | + "<\u20aa" // new shekel sign | ||
| 215 | + "<\u20a9" // won sign | ||
| 216 | + "<\u00a5" // yen sign | ||
| 217 | + "<'\u002a'" // asterisk | ||
| 218 | + "<'\\'" // backslash | ||
| 219 | + "<'\u0026'" // ampersand | ||
| 220 | + "<'\u0023'" // number sign | ||
| 221 | + "<'\u0025'" // percent sign | ||
| 222 | + "<'\u002b'" // plus sign | ||
| 223 | + "<\u00b1" // plus-or-minus sign | ||
| 224 | + "<\u00f7" // divide sign | ||
| 225 | + "<\u00d7" // multiply sign | ||
| 226 | + "<'\u003c'" // less-than sign | ||
| 227 | + "<'\u003d'" // equal sign | ||
| 228 | + "<'\u003e'" // greater-than sign | ||
| 229 | + "<\u00ac" // end of line symbol/logical NOT symbol | ||
| 230 | + "<'\u007c'" // vertical line/logical OR symbol | ||
| 231 | + "<\u00a6" // broken vertical line | ||
| 232 | + "<\u00b0" // degree symbol | ||
| 233 | + "<\u00b5" // micro symbol | ||
| 234 | |||
| 235 | // NUMERICS | ||
| 236 | |||
| 237 | + "<0<1<2<3<4<5<6<7<8<9" | ||
| 238 | + "<\u00bc<\u00bd<\u00be" // 1/4,1/2,3/4 fractions | ||
| 239 | |||
| 240 | // NON-IGNORABLES | ||
| 241 | + "<a,A" | ||
| 242 | + "<b,B" | ||
| 243 | + "<c,C" | ||
| 244 | + "<d,D" | ||
| 245 | + "<\u00F0,\u00D0" // eth | ||
| 246 | + "<e,E" | ||
| 247 | + "<f,F" | ||
| 248 | + "<g,G" | ||
| 249 | + "<h,H" | ||
| 250 | + "<i,I" | ||
| 251 | + "<j,J" | ||
| 252 | + "<k,K" | ||
| 253 | + "<l,L" | ||
| 254 | + "<m,M" | ||
| 255 | + "<n,N" | ||
| 256 | + "<o,O" | ||
| 257 | + "<p,P" | ||
| 258 | + "<q,Q" | ||
| 259 | + "<r,R" | ||
| 260 | + "<s, S & SS,\u00DF" // s-zet | ||
| 261 | + "<t,T" | ||
| 262 | + "& TH, \u00DE &TH, \u00FE " // thorn | ||
| 263 | + "<u,U" | ||
| 264 | + "<v,V" | ||
| 265 | + "<w,W" | ||
| 266 | + "<x,X" | ||
| 267 | + "<y,Y" | ||
| 268 | + "<z,Z" | ||
| 269 | + "&AE,\u00C6" // ae & AE ligature | ||
| 270 | + "&AE,\u00E6" | ||
| 271 | + "&OE,\u0152" // oe & OE ligature | ||
| 272 | + "&OE,\u0153" | ||
| 273 | ); | ||
| 274 | } | ||
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,553 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996-1998 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.text.spi.CollatorProvider; | ||
| 42 | import java.util.Locale; | ||
| 43 | import java.util.MissingResourceException; | ||
| 44 | import java.util.ResourceBundle; | ||
| 45 | import java.util.spi.LocaleServiceProvider; | ||
| 46 | import sun.misc.SoftCache; | ||
| 47 | import sun.util.resources.LocaleData; | ||
| 48 | import sun.util.LocaleServiceProviderPool; | ||
| 49 | |||
| 50 | |||
| 51 | /** | ||
| 52 | * The <code>Collator</code> class performs locale-sensitive | ||
| 53 | * <code>String</code> comparison. You use this class to build | ||
| 54 | * searching and sorting routines for natural language text. | ||
| 55 | * | ||
| 56 | * <p> | ||
| 57 | * <code>Collator</code> is an abstract base class. Subclasses | ||
| 58 | * implement specific collation strategies. One subclass, | ||
| 59 | * <code>RuleBasedCollator</code>, is currently provided with | ||
| 60 | * the Java Platform and is applicable to a wide set of languages. Other | ||
| 61 | * subclasses may be created to handle more specialized needs. | ||
| 62 | * | ||
| 63 | * <p> | ||
| 64 | * Like other locale-sensitive classes, you can use the static | ||
| 65 | * factory method, <code>getInstance</code>, to obtain the appropriate | ||
| 66 | * <code>Collator</code> object for a given locale. You will only need | ||
| 67 | * to look at the subclasses of <code>Collator</code> if you need | ||
| 68 | * to understand the details of a particular collation strategy or | ||
| 69 | * if you need to modify that strategy. | ||
| 70 | * | ||
| 71 | * <p> | ||
| 72 | * The following example shows how to compare two strings using | ||
| 73 | * the <code>Collator</code> for the default locale. | ||
| 74 | * <blockquote> | ||
| 75 | * <pre> | ||
| 76 | * // Compare two strings in the default locale | ||
| 77 | * Collator myCollator = Collator.getInstance(); | ||
| 78 | * if( myCollator.compare("abc", "ABC") < 0 ) | ||
| 79 | * System.out.println("abc is less than ABC"); | ||
| 80 | * else | ||
| 81 | * System.out.println("abc is greater than or equal to ABC"); | ||
| 82 | * </pre> | ||
| 83 | * </blockquote> | ||
| 84 | * | ||
| 85 | * <p> | ||
| 86 | * You can set a <code>Collator</code>'s <em>strength</em> property | ||
| 87 | * to determine the level of difference considered significant in | ||
| 88 | * comparisons. Four strengths are provided: <code>PRIMARY</code>, | ||
| 89 | * <code>SECONDARY</code>, <code>TERTIARY</code>, and <code>IDENTICAL</code>. | ||
| 90 | * The exact assignment of strengths to language features is | ||
| 91 | * locale dependant. For example, in Czech, "e" and "f" are considered | ||
| 92 | * primary differences, while "e" and "ě" are secondary differences, | ||
| 93 | * "e" and "E" are tertiary differences and "e" and "e" are identical. | ||
| 94 | * The following shows how both case and accents could be ignored for | ||
| 95 | * US English. | ||
| 96 | * <blockquote> | ||
| 97 | * <pre> | ||
| 98 | * //Get the Collator for US English and set its strength to PRIMARY | ||
| 99 | * Collator usCollator = Collator.getInstance(Locale.US); | ||
| 100 | * usCollator.setStrength(Collator.PRIMARY); | ||
| 101 | * if( usCollator.compare("abc", "ABC") == 0 ) { | ||
| 102 | * System.out.println("Strings are equivalent"); | ||
| 103 | * } | ||
| 104 | * </pre> | ||
| 105 | * </blockquote> | ||
| 106 | * <p> | ||
| 107 | * For comparing <code>String</code>s exactly once, the <code>compare</code> | ||
| 108 | * method provides the best performance. When sorting a list of | ||
| 109 | * <code>String</code>s however, it is generally necessary to compare each | ||
| 110 | * <code>String</code> multiple times. In this case, <code>CollationKey</code>s | ||
| 111 | * provide better performance. The <code>CollationKey</code> class converts | ||
| 112 | * a <code>String</code> to a series of bits that can be compared bitwise | ||
| 113 | * against other <code>CollationKey</code>s. A <code>CollationKey</code> is | ||
| 114 | * created by a <code>Collator</code> object for a given <code>String</code>. | ||
| 115 | * <br> | ||
| 116 | * <strong>Note:</strong> <code>CollationKey</code>s from different | ||
| 117 | * <code>Collator</code>s can not be compared. See the class description | ||
| 118 | * for {@link CollationKey} | ||
| 119 | * for an example using <code>CollationKey</code>s. | ||
| 120 | * | ||
| 121 | * @see RuleBasedCollator | ||
| 122 | * @see CollationKey | ||
| 123 | * @see CollationElementIterator | ||
| 124 | * @see Locale | ||
| 125 | * @author Helena Shih, Laura Werner, Richard Gillam | ||
| 126 | */ | ||
| 127 | |||
| 128 | public abstract class Collator | ||
| 129 | implements java.util.Comparator<Object>, Cloneable | ||
| 130 | { | ||
| 131 | /** | ||
| 132 | * Collator strength value. When set, only PRIMARY differences are | ||
| 133 | * considered significant during comparison. The assignment of strengths | ||
| 134 | * to language features is locale dependant. A common example is for | ||
| 135 | * different base letters ("a" vs "b") to be considered a PRIMARY difference. | ||
| 136 | * @see java.text.Collator#setStrength | ||
| 137 | * @see java.text.Collator#getStrength | ||
| 138 | */ | ||
| 139 | public final static int PRIMARY = 0; | ||
| 140 | /** | ||
| 141 | * Collator strength value. When set, only SECONDARY and above differences are | ||
| 142 | * considered significant during comparison. The assignment of strengths | ||
| 143 | * to language features is locale dependant. A common example is for | ||
| 144 | * different accented forms of the same base letter ("a" vs "\u00E4") to be | ||
| 145 | * considered a SECONDARY difference. | ||
| 146 | * @see java.text.Collator#setStrength | ||
| 147 | * @see java.text.Collator#getStrength | ||
| 148 | */ | ||
| 149 | public final static int SECONDARY = 1; | ||
| 150 | /** | ||
| 151 | * Collator strength value. When set, only TERTIARY and above differences are | ||
| 152 | * considered significant during comparison. The assignment of strengths | ||
| 153 | * to language features is locale dependant. A common example is for | ||
| 154 | * case differences ("a" vs "A") to be considered a TERTIARY difference. | ||
| 155 | * @see java.text.Collator#setStrength | ||
| 156 | * @see java.text.Collator#getStrength | ||
| 157 | */ | ||
| 158 | public final static int TERTIARY = 2; | ||
| 159 | |||
| 160 | /** | ||
| 161 | * Collator strength value. When set, all differences are | ||
| 162 | * considered significant during comparison. The assignment of strengths | ||
| 163 | * to language features is locale dependant. A common example is for control | ||
| 164 | * characters ("\u0001" vs "\u0002") to be considered equal at the | ||
| 165 | * PRIMARY, SECONDARY, and TERTIARY levels but different at the IDENTICAL | ||
| 166 | * level. Additionally, differences between pre-composed accents such as | ||
| 167 | * "\u00C0" (A-grave) and combining accents such as "A\u0300" | ||
| 168 | * (A, combining-grave) will be considered significant at the IDENTICAL | ||
| 169 | * level if decomposition is set to NO_DECOMPOSITION. | ||
| 170 | */ | ||
| 171 | public final static int IDENTICAL = 3; | ||
| 172 | |||
| 173 | /** | ||
| 174 | * Decomposition mode value. With NO_DECOMPOSITION | ||
| 175 | * set, accented characters will not be decomposed for collation. This | ||
| 176 | * is the default setting and provides the fastest collation but | ||
| 177 | * will only produce correct results for languages that do not use accents. | ||
| 178 | * @see java.text.Collator#getDecomposition | ||
| 179 | * @see java.text.Collator#setDecomposition | ||
| 180 | */ | ||
| 181 | public final static int NO_DECOMPOSITION = 0; | ||
| 182 | |||
| 183 | /** | ||
| 184 | * Decomposition mode value. With CANONICAL_DECOMPOSITION | ||
| 185 | * set, characters that are canonical variants according to Unicode | ||
| 186 | * standard will be decomposed for collation. This should be used to get | ||
| 187 | * correct collation of accented characters. | ||
| 188 | * <p> | ||
| 189 | * CANONICAL_DECOMPOSITION corresponds to Normalization Form D as | ||
| 190 | * described in | ||
| 191 | * <a href="http://www.unicode.org/unicode/reports/tr15/tr15-23.html">Unicode | ||
| 192 | * Technical Report #15</a>. | ||
| 193 | * @see java.text.Collator#getDecomposition | ||
| 194 | * @see java.text.Collator#setDecomposition | ||
| 195 | */ | ||
| 196 | public final static int CANONICAL_DECOMPOSITION = 1; | ||
| 197 | |||
| 198 | /** | ||
| 199 | * Decomposition mode value. With FULL_DECOMPOSITION | ||
| 200 | * set, both Unicode canonical variants and Unicode compatibility variants | ||
| 201 | * will be decomposed for collation. This causes not only accented | ||
| 202 | * characters to be collated, but also characters that have special formats | ||
| 203 | * to be collated with their norminal form. For example, the half-width and | ||
| 204 | * full-width ASCII and Katakana characters are then collated together. | ||
| 205 | * FULL_DECOMPOSITION is the most complete and therefore the slowest | ||
| 206 | * decomposition mode. | ||
| 207 | * <p> | ||
| 208 | * FULL_DECOMPOSITION corresponds to Normalization Form KD as | ||
| 209 | * described in | ||
| 210 | * <a href="http://www.unicode.org/unicode/reports/tr15/tr15-23.html">Unicode | ||
| 211 | * Technical Report #15</a>. | ||
| 212 | * @see java.text.Collator#getDecomposition | ||
| 213 | * @see java.text.Collator#setDecomposition | ||
| 214 | */ | ||
| 215 | public final static int FULL_DECOMPOSITION = 2; | ||
| 216 | |||
| 217 | /** | ||
| 218 | * Gets the Collator for the current default locale. | ||
| 219 | * The default locale is determined by java.util.Locale.getDefault. | ||
| 220 | * @return the Collator for the default locale.(for example, en_US) | ||
| 221 | * @see java.util.Locale#getDefault | ||
| 222 | */ | ||
| 223 | public static synchronized Collator getInstance() { | ||
| 224 | return getInstance(Locale.getDefault()); | ||
| 225 | } | ||
| 226 | |||
| 227 | /** | ||
| 228 | * Gets the Collator for the desired locale. | ||
| 229 | * @param desiredLocale the desired locale. | ||
| 230 | * @return the Collator for the desired locale. | ||
| 231 | * @see java.util.Locale | ||
| 232 | * @see java.util.ResourceBundle | ||
| 233 | */ | ||
| 234 | public static synchronized | ||
| 235 | Collator getInstance(Locale desiredLocale) | ||
| 236 | { | ||
| 237 | Collator result = (Collator) cache.get(desiredLocale); | ||
| 238 | if (result != null) { | ||
| 239 | return (Collator)result.clone(); // make the world safe | ||
| 240 | } | ||
| 241 | |||
| 242 | // Check whether a provider can provide an implementation that's closer | ||
| 243 | // to the requested locale than what the Java runtime itself can provide. | ||
| 244 | LocaleServiceProviderPool pool = | ||
| 245 | LocaleServiceProviderPool.getPool(CollatorProvider.class); | ||
| 246 | if (pool.hasProviders()) { | ||
| 247 | Collator providersInstance = pool.getLocalizedObject( | ||
| 248 | CollatorGetter.INSTANCE, | ||
| 249 | desiredLocale, | ||
| 250 | desiredLocale); | ||
| 251 | if (providersInstance != null) { | ||
| 252 | return providersInstance; | ||
| 253 | } | ||
| 254 | } | ||
| 255 | |||
| 256 | // Load the resource of the desired locale from resource | ||
| 257 | // manager. | ||
| 258 | String colString = ""; | ||
| 259 | try { | ||
| 260 | ResourceBundle resource = LocaleData.getCollationData(desiredLocale); | ||
| 261 | |||
| 262 | colString = resource.getString("Rule"); | ||
| 263 | } catch (MissingResourceException e) { | ||
| 264 | // Use default values | ||
| 265 | } | ||
| 266 | try | ||
| 267 | { | ||
| 268 | result = new RuleBasedCollator( CollationRules.DEFAULTRULES + | ||
| 269 | colString, | ||
| 270 | CANONICAL_DECOMPOSITION ); | ||
| 271 | } | ||
| 272 | catch(ParseException foo) | ||
| 273 | { | ||
| 274 | // predefined tables should contain correct grammar | ||
| 275 | try { | ||
| 276 | result = new RuleBasedCollator( CollationRules.DEFAULTRULES ); | ||
| 277 | } catch (ParseException bar) { | ||
| 278 | // do nothing | ||
| 279 | } | ||
| 280 | } | ||
| 281 | // Now that RuleBasedCollator adds expansions for pre-composed characters | ||
| 282 | // into their decomposed equivalents, the default collators don't need | ||
| 283 | // to have decomposition turned on. Laura, 5/5/98, bug 4114077 | ||
| 284 | result.setDecomposition(NO_DECOMPOSITION); | ||
| 285 | |||
| 286 | cache.put(desiredLocale,result); | ||
| 287 | return (Collator)result.clone(); | ||
| 288 | } | ||
| 289 | |||
| 290 | /** | ||
| 291 | * Compares the source string to the target string according to the | ||
| 292 | * collation rules for this Collator. Returns an integer less than, | ||
| 293 | * equal to or greater than zero depending on whether the source String is | ||
| 294 | * less than, equal to or greater than the target string. See the Collator | ||
| 295 | * class description for an example of use. | ||
| 296 | * <p> | ||
| 297 | * For a one time comparison, this method has the best performance. If a | ||
| 298 | * given String will be involved in multiple comparisons, CollationKey.compareTo | ||
| 299 | * has the best performance. See the Collator class description for an example | ||
| 300 | * using CollationKeys. | ||
| 301 | * @param source the source string. | ||
| 302 | * @param target the target string. | ||
| 303 | * @return Returns an integer value. Value is less than zero if source is less than | ||
| 304 | * target, value is zero if source and target are equal, value is greater than zero | ||
| 305 | * if source is greater than target. | ||
| 306 | * @see java.text.CollationKey | ||
| 307 | * @see java.text.Collator#getCollationKey | ||
| 308 | */ | ||
| 309 | public abstract int compare(String source, String target); | ||
| 310 | |||
| 311 | /** | ||
| 312 | * Compares its two arguments for order. Returns a negative integer, | ||
| 313 | * zero, or a positive integer as the first argument is less than, equal | ||
| 314 | * to, or greater than the second. | ||
| 315 | * <p> | ||
| 316 | * This implementation merely returns | ||
| 317 | * <code> compare((String)o1, (String)o2) </code>. | ||
| 318 | * | ||
| 319 | * @return a negative integer, zero, or a positive integer as the | ||
| 320 | * first argument is less than, equal to, or greater than the | ||
| 321 | * second. | ||
| 322 | * @exception ClassCastException the arguments cannot be cast to Strings. | ||
| 323 | * @see java.util.Comparator | ||
| 324 | * @since 1.2 | ||
| 325 | */ | ||
| 326 | public int compare(Object o1, Object o2) { | ||
| 327 | return compare((String)o1, (String)o2); | ||
| 328 | } | ||
| 329 | |||
| 330 | /** | ||
| 331 | * Transforms the String into a series of bits that can be compared bitwise | ||
| 332 | * to other CollationKeys. CollationKeys provide better performance than | ||
| 333 | * Collator.compare when Strings are involved in multiple comparisons. | ||
| 334 | * See the Collator class description for an example using CollationKeys. | ||
| 335 | * @param source the string to be transformed into a collation key. | ||
| 336 | * @return the CollationKey for the given String based on this Collator's collation | ||
| 337 | * rules. If the source String is null, a null CollationKey is returned. | ||
| 338 | * @see java.text.CollationKey | ||
| 339 | * @see java.text.Collator#compare | ||
| 340 | */ | ||
| 341 | public abstract CollationKey getCollationKey(String source); | ||
| 342 | |||
| 343 | /** | ||
| 344 | * Convenience method for comparing the equality of two strings based on | ||
| 345 | * this Collator's collation rules. | ||
| 346 | * @param source the source string to be compared with. | ||
| 347 | * @param target the target string to be compared with. | ||
| 348 | * @return true if the strings are equal according to the collation | ||
| 349 | * rules. false, otherwise. | ||
| 350 | * @see java.text.Collator#compare | ||
| 351 | */ | ||
| 352 | public boolean equals(String source, String target) | ||
| 353 | { | ||
| 354 | return (compare(source, target) == Collator.EQUAL); | ||
| 355 | } | ||
| 356 | |||
| 357 | /** | ||
| 358 | * Returns this Collator's strength property. The strength property determines | ||
| 359 | * the minimum level of difference considered significant during comparison. | ||
| 360 | * See the Collator class description for an example of use. | ||
| 361 | * @return this Collator's current strength property. | ||
| 362 | * @see java.text.Collator#setStrength | ||
| 363 | * @see java.text.Collator#PRIMARY | ||
| 364 | * @see java.text.Collator#SECONDARY | ||
| 365 | * @see java.text.Collator#TERTIARY | ||
| 366 | * @see java.text.Collator#IDENTICAL | ||
| 367 | */ | ||
| 368 | public synchronized int getStrength() | ||
| 369 | { | ||
| 370 | return strength; | ||
| 371 | } | ||
| 372 | |||
| 373 | /** | ||
| 374 | * Sets this Collator's strength property. The strength property determines | ||
| 375 | * the minimum level of difference considered significant during comparison. | ||
| 376 | * See the Collator class description for an example of use. | ||
| 377 | * @param newStrength the new strength value. | ||
| 378 | * @see java.text.Collator#getStrength | ||
| 379 | * @see java.text.Collator#PRIMARY | ||
| 380 | * @see java.text.Collator#SECONDARY | ||
| 381 | * @see java.text.Collator#TERTIARY | ||
| 382 | * @see java.text.Collator#IDENTICAL | ||
| 383 | * @exception IllegalArgumentException If the new strength value is not one of | ||
| 384 | * PRIMARY, SECONDARY, TERTIARY or IDENTICAL. | ||
| 385 | */ | ||
| 386 | public synchronized void setStrength(int newStrength) { | ||
| 387 | if ((newStrength != PRIMARY) && | ||
| 388 | (newStrength != SECONDARY) && | ||
| 389 | (newStrength != TERTIARY) && | ||
| 390 | (newStrength != IDENTICAL)) | ||
| 391 | throw new IllegalArgumentException("Incorrect comparison level."); | ||
| 392 | strength = newStrength; | ||
| 393 | } | ||
| 394 | |||
| 395 | /** | ||
| 396 | * Get the decomposition mode of this Collator. Decomposition mode | ||
| 397 | * determines how Unicode composed characters are handled. Adjusting | ||
| 398 | * decomposition mode allows the user to select between faster and more | ||
| 399 | * complete collation behavior. | ||
| 400 | * <p>The three values for decomposition mode are: | ||
| 401 | * <UL> | ||
| 402 | * <LI>NO_DECOMPOSITION, | ||
| 403 | * <LI>CANONICAL_DECOMPOSITION | ||
| 404 | * <LI>FULL_DECOMPOSITION. | ||
| 405 | * </UL> | ||
| 406 | * See the documentation for these three constants for a description | ||
| 407 | * of their meaning. | ||
| 408 | * @return the decomposition mode | ||
| 409 | * @see java.text.Collator#setDecomposition | ||
| 410 | * @see java.text.Collator#NO_DECOMPOSITION | ||
| 411 | * @see java.text.Collator#CANONICAL_DECOMPOSITION | ||
| 412 | * @see java.text.Collator#FULL_DECOMPOSITION | ||
| 413 | */ | ||
| 414 | public synchronized int getDecomposition() | ||
| 415 | { | ||
| 416 | return decmp; | ||
| 417 | } | ||
| 418 | /** | ||
| 419 | * Set the decomposition mode of this Collator. See getDecomposition | ||
| 420 | * for a description of decomposition mode. | ||
| 421 | * @param decompositionMode the new decomposition mode. | ||
| 422 | * @see java.text.Collator#getDecomposition | ||
| 423 | * @see java.text.Collator#NO_DECOMPOSITION | ||
| 424 | * @see java.text.Collator#CANONICAL_DECOMPOSITION | ||
| 425 | * @see java.text.Collator#FULL_DECOMPOSITION | ||
| 426 | * @exception IllegalArgumentException If the given value is not a valid decomposition | ||
| 427 | * mode. | ||
| 428 | */ | ||
| 429 | public synchronized void setDecomposition(int decompositionMode) { | ||
| 430 | if ((decompositionMode != NO_DECOMPOSITION) && | ||
| 431 | (decompositionMode != CANONICAL_DECOMPOSITION) && | ||
| 432 | (decompositionMode != FULL_DECOMPOSITION)) | ||
| 433 | throw new IllegalArgumentException("Wrong decomposition mode."); | ||
| 434 | decmp = decompositionMode; | ||
| 435 | } | ||
| 436 | |||
| 437 | /** | ||
| 438 | * Returns an array of all locales for which the | ||
| 439 | * <code>getInstance</code> methods of this class can return | ||
| 440 | * localized instances. | ||
| 441 | * The returned array represents the union of locales supported | ||
| 442 | * by the Java runtime and by installed | ||
| 443 | * {@link java.text.spi.CollatorProvider CollatorProvider} implementations. | ||
| 444 | * It must contain at least a Locale instance equal to | ||
| 445 | * {@link java.util.Locale#US Locale.US}. | ||
| 446 | * | ||
| 447 | * @return An array of locales for which localized | ||
| 448 | * <code>Collator</code> instances are available. | ||
| 449 | */ | ||
| 450 | public static synchronized Locale[] getAvailableLocales() { | ||
| 451 | LocaleServiceProviderPool pool = | ||
| 452 | LocaleServiceProviderPool.getPool(CollatorProvider.class); | ||
| 453 | return pool.getAvailableLocales(); | ||
| 454 | } | ||
| 455 | |||
| 456 | /** | ||
| 457 | * Overrides Cloneable | ||
| 458 | */ | ||
| 459 | public Object clone() | ||
| 460 | { | ||
| 461 | try { | ||
| 462 | return (Collator)super.clone(); | ||
| 463 | } catch (CloneNotSupportedException e) { | ||
| 464 | throw new InternalError(); | ||
| 465 | } | ||
| 466 | } | ||
| 467 | |||
| 468 | /** | ||
| 469 | * Compares the equality of two Collators. | ||
| 470 | * @param that the Collator to be compared with this. | ||
| 471 | * @return true if this Collator is the same as that Collator; | ||
| 472 | * false otherwise. | ||
| 473 | */ | ||
| 474 | public boolean equals(Object that) | ||
| 475 | { | ||
| 476 | if (this == that) return true; | ||
| 477 | if (that == null) return false; | ||
| 478 | if (getClass() != that.getClass()) return false; | ||
| 479 | Collator other = (Collator) that; | ||
| 480 | return ((strength == other.strength) && | ||
| 481 | (decmp == other.decmp)); | ||
| 482 | } | ||
| 483 | |||
| 484 | /** | ||
| 485 | * Generates the hash code for this Collator. | ||
| 486 | */ | ||
| 487 | abstract public int hashCode(); | ||
| 488 | |||
| 489 | /** | ||
| 490 | * Default constructor. This constructor is | ||
| 491 | * protected so subclasses can get access to it. Users typically create | ||
| 492 | * a Collator sub-class by calling the factory method getInstance. | ||
| 493 | * @see java.text.Collator#getInstance | ||
| 494 | */ | ||
| 495 | protected Collator() | ||
| 496 | { | ||
| 497 | strength = TERTIARY; | ||
| 498 | decmp = CANONICAL_DECOMPOSITION; | ||
| 499 | } | ||
| 500 | |||
| 501 | private int strength = 0; | ||
| 502 | private int decmp = 0; | ||
| 503 | private static SoftCache cache = new SoftCache(); | ||
| 504 | |||
| 505 | // | ||
| 506 | // FIXME: These three constants should be removed. | ||
| 507 | // | ||
| 508 | /** | ||
| 509 | * LESS is returned if source string is compared to be less than target | ||
| 510 | * string in the compare() method. | ||
| 511 | * @see java.text.Collator#compare | ||
| 512 | */ | ||
| 513 | final static int LESS = -1; | ||
| 514 | /** | ||
| 515 | * EQUAL is returned if source string is compared to be equal to target | ||
| 516 | * string in the compare() method. | ||
| 517 | * @see java.text.Collator#compare | ||
| 518 | */ | ||
| 519 | final static int EQUAL = 0; | ||
| 520 | /** | ||
| 521 | * GREATER is returned if source string is compared to be greater than | ||
| 522 | * target string in the compare() method. | ||
| 523 | * @see java.text.Collator#compare | ||
| 524 | */ | ||
| 525 | final static int GREATER = 1; | ||
| 526 | |||
| 527 | /** | ||
| 528 | * Obtains a Collator instance from a CollatorProvider | ||
| 529 | * implementation. | ||
| 530 | */ | ||
| 531 | private static class CollatorGetter | ||
| 532 | implements LocaleServiceProviderPool.LocalizedObjectGetter<CollatorProvider, Collator> { | ||
| 533 | private static final CollatorGetter INSTANCE = new CollatorGetter(); | ||
| 534 | |||
| 535 | public Collator getObject(CollatorProvider collatorProvider, | ||
| 536 | Locale locale, | ||
| 537 | String key, | ||
| 538 | Object... params) { | ||
| 539 | assert params.length == 1; | ||
| 540 | Collator result = collatorProvider.getInstance(locale); | ||
| 541 | if (result != null) { | ||
| 542 | // put this Collator instance in the cache for two locales, one | ||
| 543 | // is for the desired locale, and the other is for the actual | ||
| 544 | // locale where the provider is found, which may be a fall back locale. | ||
| 545 | cache.put((Locale)params[0], result); | ||
| 546 | cache.put(locale, result); | ||
| 547 | return (Collator)result.clone(); | ||
| 548 | } | ||
| 549 | |||
| 550 | return null; | ||
| 551 | } | ||
| 552 | } | ||
| 553 | } | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,1030 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.io.InvalidObjectException; | ||
| 42 | import java.text.spi.DateFormatProvider; | ||
| 43 | import java.util.Calendar; | ||
| 44 | import java.util.Date; | ||
| 45 | import java.util.GregorianCalendar; | ||
| 46 | import java.util.HashMap; | ||
| 47 | import java.util.Locale; | ||
| 48 | import java.util.Map; | ||
| 49 | import java.util.MissingResourceException; | ||
| 50 | import java.util.ResourceBundle; | ||
| 51 | import java.util.TimeZone; | ||
| 52 | import java.util.spi.LocaleServiceProvider; | ||
| 53 | import sun.util.LocaleServiceProviderPool; | ||
| 54 | |||
| 55 | /** | ||
| 56 | * {@code DateFormat} is an abstract class for date/time formatting subclasses which | ||
| 57 | * formats and parses dates or time in a language-independent manner. | ||
| 58 | * The date/time formatting subclass, such as {@link SimpleDateFormat}, allows for | ||
| 59 | * formatting (i.e., date -> text), parsing (text -> date), and | ||
| 60 | * normalization. The date is represented as a <code>Date</code> object or | ||
| 61 | * as the milliseconds since January 1, 1970, 00:00:00 GMT. | ||
| 62 | * | ||
| 63 | * <p>{@code DateFormat} provides many class methods for obtaining default date/time | ||
| 64 | * formatters based on the default or a given locale and a number of formatting | ||
| 65 | * styles. The formatting styles include {@link #FULL}, {@link #LONG}, {@link #MEDIUM}, and {@link #SHORT}. More | ||
| 66 | * detail and examples of using these styles are provided in the method | ||
| 67 | * descriptions. | ||
| 68 | * | ||
| 69 | * <p>{@code DateFormat} helps you to format and parse dates for any locale. | ||
| 70 | * Your code can be completely independent of the locale conventions for | ||
| 71 | * months, days of the week, or even the calendar format: lunar vs. solar. | ||
| 72 | * | ||
| 73 | * <p>To format a date for the current Locale, use one of the | ||
| 74 | * static factory methods: | ||
| 75 | * <pre> | ||
| 76 | * myString = DateFormat.getDateInstance().format(myDate); | ||
| 77 | * </pre> | ||
| 78 | * <p>If you are formatting multiple dates, it is | ||
| 79 | * more efficient to get the format and use it multiple times so that | ||
| 80 | * the system doesn't have to fetch the information about the local | ||
| 81 | * language and country conventions multiple times. | ||
| 82 | * <pre> | ||
| 83 | * DateFormat df = DateFormat.getDateInstance(); | ||
| 84 | * for (int i = 0; i < myDate.length; ++i) { | ||
| 85 | * output.println(df.format(myDate[i]) + "; "); | ||
| 86 | * } | ||
| 87 | * </pre> | ||
| 88 | * <p>To format a date for a different Locale, specify it in the | ||
| 89 | * call to {@link #getDateInstance(int, Locale) getDateInstance()}. | ||
| 90 | * <pre> | ||
| 91 | * DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE); | ||
| 92 | * </pre> | ||
| 93 | * <p>You can use a DateFormat to parse also. | ||
| 94 | * <pre> | ||
| 95 | * myDate = df.parse(myString); | ||
| 96 | * </pre> | ||
| 97 | * <p>Use {@code getDateInstance} to get the normal date format for that country. | ||
| 98 | * There are other static factory methods available. | ||
| 99 | * Use {@code getTimeInstance} to get the time format for that country. | ||
| 100 | * Use {@code getDateTimeInstance} to get a date and time format. You can pass in | ||
| 101 | * different options to these factory methods to control the length of the | ||
| 102 | * result; from {@link #SHORT} to {@link #MEDIUM} to {@link #LONG} to {@link #FULL}. The exact result depends | ||
| 103 | * on the locale, but generally: | ||
| 104 | * <ul><li>{@link #SHORT} is completely numeric, such as {@code 12.13.52} or {@code 3:30pm} | ||
| 105 | * <li>{@link #MEDIUM} is longer, such as {@code Jan 12, 1952} | ||
| 106 | * <li>{@link #LONG} is longer, such as {@code January 12, 1952} or {@code 3:30:32pm} | ||
| 107 | * <li>{@link #FULL} is pretty completely specified, such as | ||
| 108 | * {@code Tuesday, April 12, 1952 AD or 3:30:42pm PST}. | ||
| 109 | * </ul> | ||
| 110 | * | ||
| 111 | * <p>You can also set the time zone on the format if you wish. | ||
| 112 | * If you want even more control over the format or parsing, | ||
| 113 | * (or want to give your users more control), | ||
| 114 | * you can try casting the {@code DateFormat} you get from the factory methods | ||
| 115 | * to a {@link SimpleDateFormat}. This will work for the majority | ||
| 116 | * of countries; just remember to put it in a {@code try} block in case you | ||
| 117 | * encounter an unusual one. | ||
| 118 | * | ||
| 119 | * <p>You can also use forms of the parse and format methods with | ||
| 120 | * {@link ParsePosition} and {@link FieldPosition} to | ||
| 121 | * allow you to | ||
| 122 | * <ul><li>progressively parse through pieces of a string. | ||
| 123 | * <li>align any particular field, or find out where it is for selection | ||
| 124 | * on the screen. | ||
| 125 | * </ul> | ||
| 126 | * | ||
| 127 | * <h4><a name="synchronization">Synchronization</a></h4> | ||
| 128 | * | ||
| 129 | * <p> | ||
| 130 | * Date formats are not synchronized. | ||
| 131 | * It is recommended to create separate format instances for each thread. | ||
| 132 | * If multiple threads access a format concurrently, it must be synchronized | ||
| 133 | * externally. | ||
| 134 | * | ||
| 135 | * @see Format | ||
| 136 | * @see NumberFormat | ||
| 137 | * @see SimpleDateFormat | ||
| 138 | * @see java.util.Calendar | ||
| 139 | * @see java.util.GregorianCalendar | ||
| 140 | * @see java.util.TimeZone | ||
| 141 | * @author Mark Davis, Chen-Lieh Huang, Alan Liu | ||
| 142 | */ | ||
| 143 | public abstract class DateFormat extends Format { | ||
| 144 | |||
| 145 | /** | ||
| 146 | * The {@link Calendar} instance used for calculating the date-time fields | ||
| 147 | * and the instant of time. This field is used for both formatting and | ||
| 148 | * parsing. | ||
| 149 | * | ||
| 150 | * <p>Subclasses should initialize this field to a {@link Calendar} | ||
| 151 | * appropriate for the {@link Locale} associated with this | ||
| 152 | * <code>DateFormat</code>. | ||
| 153 | * @serial | ||
| 154 | */ | ||
| 155 | protected Calendar calendar; | ||
| 156 | |||
| 157 | /** | ||
| 158 | * The number formatter that <code>DateFormat</code> uses to format numbers | ||
| 159 | * in dates and times. Subclasses should initialize this to a number format | ||
| 160 | * appropriate for the locale associated with this <code>DateFormat</code>. | ||
| 161 | * @serial | ||
| 162 | */ | ||
| 163 | protected NumberFormat numberFormat; | ||
| 164 | |||
| 165 | /** | ||
| 166 | * Useful constant for ERA field alignment. | ||
| 167 | * Used in FieldPosition of date/time formatting. | ||
| 168 | */ | ||
| 169 | public final static int ERA_FIELD = 0; | ||
| 170 | /** | ||
| 171 | * Useful constant for YEAR field alignment. | ||
| 172 | * Used in FieldPosition of date/time formatting. | ||
| 173 | */ | ||
| 174 | public final static int YEAR_FIELD = 1; | ||
| 175 | /** | ||
| 176 | * Useful constant for MONTH field alignment. | ||
| 177 | * Used in FieldPosition of date/time formatting. | ||
| 178 | */ | ||
| 179 | public final static int MONTH_FIELD = 2; | ||
| 180 | /** | ||
| 181 | * Useful constant for DATE field alignment. | ||
| 182 | * Used in FieldPosition of date/time formatting. | ||
| 183 | */ | ||
| 184 | public final static int DATE_FIELD = 3; | ||
| 185 | /** | ||
| 186 | * Useful constant for one-based HOUR_OF_DAY field alignment. | ||
| 187 | * Used in FieldPosition of date/time formatting. | ||
| 188 | * HOUR_OF_DAY1_FIELD is used for the one-based 24-hour clock. | ||
| 189 | * For example, 23:59 + 01:00 results in 24:59. | ||
| 190 | */ | ||
| 191 | public final static int HOUR_OF_DAY1_FIELD = 4; | ||
| 192 | /** | ||
| 193 | * Useful constant for zero-based HOUR_OF_DAY field alignment. | ||
| 194 | * Used in FieldPosition of date/time formatting. | ||
| 195 | * HOUR_OF_DAY0_FIELD is used for the zero-based 24-hour clock. | ||
| 196 | * For example, 23:59 + 01:00 results in 00:59. | ||
| 197 | */ | ||
| 198 | public final static int HOUR_OF_DAY0_FIELD = 5; | ||
| 199 | /** | ||
| 200 | * Useful constant for MINUTE field alignment. | ||
| 201 | * Used in FieldPosition of date/time formatting. | ||
| 202 | */ | ||
| 203 | public final static int MINUTE_FIELD = 6; | ||
| 204 | /** | ||
| 205 | * Useful constant for SECOND field alignment. | ||
| 206 | * Used in FieldPosition of date/time formatting. | ||
| 207 | */ | ||
| 208 | public final static int SECOND_FIELD = 7; | ||
| 209 | /** | ||
| 210 | * Useful constant for MILLISECOND field alignment. | ||
| 211 | * Used in FieldPosition of date/time formatting. | ||
| 212 | */ | ||
| 213 | public final static int MILLISECOND_FIELD = 8; | ||
| 214 | /** | ||
| 215 | * Useful constant for DAY_OF_WEEK field alignment. | ||
| 216 | * Used in FieldPosition of date/time formatting. | ||
| 217 | */ | ||
| 218 | public final static int DAY_OF_WEEK_FIELD = 9; | ||
| 219 | /** | ||
| 220 | * Useful constant for DAY_OF_YEAR field alignment. | ||
| 221 | * Used in FieldPosition of date/time formatting. | ||
| 222 | */ | ||
| 223 | public final static int DAY_OF_YEAR_FIELD = 10; | ||
| 224 | /** | ||
| 225 | * Useful constant for DAY_OF_WEEK_IN_MONTH field alignment. | ||
| 226 | * Used in FieldPosition of date/time formatting. | ||
| 227 | */ | ||
| 228 | public final static int DAY_OF_WEEK_IN_MONTH_FIELD = 11; | ||
| 229 | /** | ||
| 230 | * Useful constant for WEEK_OF_YEAR field alignment. | ||
| 231 | * Used in FieldPosition of date/time formatting. | ||
| 232 | */ | ||
| 233 | public final static int WEEK_OF_YEAR_FIELD = 12; | ||
| 234 | /** | ||
| 235 | * Useful constant for WEEK_OF_MONTH field alignment. | ||
| 236 | * Used in FieldPosition of date/time formatting. | ||
| 237 | */ | ||
| 238 | public final static int WEEK_OF_MONTH_FIELD = 13; | ||
| 239 | /** | ||
| 240 | * Useful constant for AM_PM field alignment. | ||
| 241 | * Used in FieldPosition of date/time formatting. | ||
| 242 | */ | ||
| 243 | public final static int AM_PM_FIELD = 14; | ||
| 244 | /** | ||
| 245 | * Useful constant for one-based HOUR field alignment. | ||
| 246 | * Used in FieldPosition of date/time formatting. | ||
| 247 | * HOUR1_FIELD is used for the one-based 12-hour clock. | ||
| 248 | * For example, 11:30 PM + 1 hour results in 12:30 AM. | ||
| 249 | */ | ||
| 250 | public final static int HOUR1_FIELD = 15; | ||
| 251 | /** | ||
| 252 | * Useful constant for zero-based HOUR field alignment. | ||
| 253 | * Used in FieldPosition of date/time formatting. | ||
| 254 | * HOUR0_FIELD is used for the zero-based 12-hour clock. | ||
| 255 | * For example, 11:30 PM + 1 hour results in 00:30 AM. | ||
| 256 | */ | ||
| 257 | public final static int HOUR0_FIELD = 16; | ||
| 258 | /** | ||
| 259 | * Useful constant for TIMEZONE field alignment. | ||
| 260 | * Used in FieldPosition of date/time formatting. | ||
| 261 | */ | ||
| 262 | public final static int TIMEZONE_FIELD = 17; | ||
| 263 | |||
| 264 | // Proclaim serial compatibility with 1.1 FCS | ||
| 265 | private static final long serialVersionUID = 7218322306649953788L; | ||
| 266 | |||
| 267 | /** | ||
| 268 | * Overrides Format. | ||
| 269 | * Formats a time object into a time string. Examples of time objects | ||
| 270 | * are a time value expressed in milliseconds and a Date object. | ||
| 271 | * @param obj must be a Number or a Date. | ||
| 272 | * @param toAppendTo the string buffer for the returning time string. | ||
| 273 | * @return the string buffer passed in as toAppendTo, with formatted text appended. | ||
| 274 | * @param fieldPosition keeps track of the position of the field | ||
| 275 | * within the returned string. | ||
| 276 | * On input: an alignment field, | ||
| 277 | * if desired. On output: the offsets of the alignment field. For | ||
| 278 | * example, given a time text "1996.07.10 AD at 15:08:56 PDT", | ||
| 279 | * if the given fieldPosition is DateFormat.YEAR_FIELD, the | ||
| 280 | * begin index and end index of fieldPosition will be set to | ||
| 281 | * 0 and 4, respectively. | ||
| 282 | * Notice that if the same time field appears | ||
| 283 | * more than once in a pattern, the fieldPosition will be set for the first | ||
| 284 | * occurrence of that time field. For instance, formatting a Date to | ||
| 285 | * the time string "1 PM PDT (Pacific Daylight Time)" using the pattern | ||
| 286 | * "h a z (zzzz)" and the alignment field DateFormat.TIMEZONE_FIELD, | ||
| 287 | * the begin index and end index of fieldPosition will be set to | ||
| 288 | * 5 and 8, respectively, for the first occurrence of the timezone | ||
| 289 | * pattern character 'z'. | ||
| 290 | * @see java.text.Format | ||
| 291 | */ | ||
| 292 | public final StringBuffer format(Object obj, StringBuffer toAppendTo, | ||
| 293 | FieldPosition fieldPosition) | ||
| 294 | { | ||
| 295 | if (obj instanceof Date) | ||
| 296 | return format( (Date)obj, toAppendTo, fieldPosition ); | ||
| 297 | else if (obj instanceof Number) | ||
| 298 | return format( new Date(((Number)obj).longValue()), | ||
| 299 | toAppendTo, fieldPosition ); | ||
| 300 | else | ||
| 301 | throw new IllegalArgumentException("Cannot format given Object as a Date"); | ||
| 302 | } | ||
| 303 | |||
| 304 | /** | ||
| 305 | * Formats a Date into a date/time string. | ||
| 306 | * @param date a Date to be formatted into a date/time string. | ||
| 307 | * @param toAppendTo the string buffer for the returning date/time string. | ||
| 308 | * @param fieldPosition keeps track of the position of the field | ||
| 309 | * within the returned string. | ||
| 310 | * On input: an alignment field, | ||
| 311 | * if desired. On output: the offsets of the alignment field. For | ||
| 312 | * example, given a time text "1996.07.10 AD at 15:08:56 PDT", | ||
| 313 | * if the given fieldPosition is DateFormat.YEAR_FIELD, the | ||
| 314 | * begin index and end index of fieldPosition will be set to | ||
| 315 | * 0 and 4, respectively. | ||
| 316 | * Notice that if the same time field appears | ||
| 317 | * more than once in a pattern, the fieldPosition will be set for the first | ||
| 318 | * occurrence of that time field. For instance, formatting a Date to | ||
| 319 | * the time string "1 PM PDT (Pacific Daylight Time)" using the pattern | ||
| 320 | * "h a z (zzzz)" and the alignment field DateFormat.TIMEZONE_FIELD, | ||
| 321 | * the begin index and end index of fieldPosition will be set to | ||
| 322 | * 5 and 8, respectively, for the first occurrence of the timezone | ||
| 323 | * pattern character 'z'. | ||
| 324 | * @return the string buffer passed in as toAppendTo, with formatted text appended. | ||
| 325 | */ | ||
| 326 | public abstract StringBuffer format(Date date, StringBuffer toAppendTo, | ||
| 327 | FieldPosition fieldPosition); | ||
| 328 | |||
| 329 | /** | ||
| 330 | * Formats a Date into a date/time string. | ||
| 331 | * @param date the time value to be formatted into a time string. | ||
| 332 | * @return the formatted time string. | ||
| 333 | */ | ||
| 334 | public final String format(Date date) | ||
| 335 | { | ||
| 336 | return format(date, new StringBuffer(), | ||
| 337 | DontCareFieldPosition.INSTANCE).toString(); | ||
| 338 | } | ||
| 339 | |||
| 340 | /** | ||
| 341 | * Parses text from the beginning of the given string to produce a date. | ||
| 342 | * The method may not use the entire text of the given string. | ||
| 343 | * <p> | ||
| 344 | * See the {@link #parse(String, ParsePosition)} method for more information | ||
| 345 | * on date parsing. | ||
| 346 | * | ||
| 347 | * @param source A <code>String</code> whose beginning should be parsed. | ||
| 348 | * @return A <code>Date</code> parsed from the string. | ||
| 349 | * @exception ParseException if the beginning of the specified string | ||
| 350 | * cannot be parsed. | ||
| 351 | */ | ||
| 352 | public Date parse(String source) throws ParseException | ||
| 353 | { | ||
| 354 | ParsePosition pos = new ParsePosition(0); | ||
| 355 | Date result = parse(source, pos); | ||
| 356 | if (pos.index == 0) | ||
| 357 | throw new ParseException("Unparseable date: \"" + source + "\"" , | ||
| 358 | pos.errorIndex); | ||
| 359 | return result; | ||
| 360 | } | ||
| 361 | |||
| 362 | /** | ||
| 363 | * Parse a date/time string according to the given parse position. For | ||
| 364 | * example, a time text {@code "07/10/96 4:5 PM, PDT"} will be parsed into a {@code Date} | ||
| 365 | * that is equivalent to {@code Date(837039900000L)}. | ||
| 366 | * | ||
| 367 | * <p> By default, parsing is lenient: If the input is not in the form used | ||
| 368 | * by this object's format method but can still be parsed as a date, then | ||
| 369 | * the parse succeeds. Clients may insist on strict adherence to the | ||
| 370 | * format by calling {@link #setLenient(boolean) setLenient(false)}. | ||
| 371 | * | ||
| 372 | * <p>This parsing operation uses the {@link #calendar} to produce | ||
| 373 | * a {@code Date}. As a result, the {@code calendar}'s date-time | ||
| 374 | * fields and the {@code TimeZone} value may have been | ||
| 375 | * overwritten, depending on subclass implementations. Any {@code | ||
| 376 | * TimeZone} value that has previously been set by a call to | ||
| 377 | * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need | ||
| 378 | * to be restored for further operations. | ||
| 379 | * | ||
| 380 | * @param source The date/time string to be parsed | ||
| 381 | * | ||
| 382 | * @param pos On input, the position at which to start parsing; on | ||
| 383 | * output, the position at which parsing terminated, or the | ||
| 384 | * start position if the parse failed. | ||
| 385 | * | ||
| 386 | * @return A {@code Date}, or {@code null} if the input could not be parsed | ||
| 387 | */ | ||
| 388 | public abstract Date parse(String source, ParsePosition pos); | ||
| 389 | |||
| 390 | /** | ||
| 391 | * Parses text from a string to produce a <code>Date</code>. | ||
| 392 | * <p> | ||
| 393 | * The method attempts to parse text starting at the index given by | ||
| 394 | * <code>pos</code>. | ||
| 395 | * If parsing succeeds, then the index of <code>pos</code> is updated | ||
| 396 | * to the index after the last character used (parsing does not necessarily | ||
| 397 | * use all characters up to the end of the string), and the parsed | ||
| 398 | * date is returned. The updated <code>pos</code> can be used to | ||
| 399 | * indicate the starting point for the next call to this method. | ||
| 400 | * If an error occurs, then the index of <code>pos</code> is not | ||
| 401 | * changed, the error index of <code>pos</code> is set to the index of | ||
| 402 | * the character where the error occurred, and null is returned. | ||
| 403 | * <p> | ||
| 404 | * See the {@link #parse(String, ParsePosition)} method for more information | ||
| 405 | * on date parsing. | ||
| 406 | * | ||
| 407 | * @param source A <code>String</code>, part of which should be parsed. | ||
| 408 | * @param pos A <code>ParsePosition</code> object with index and error | ||
| 409 | * index information as described above. | ||
| 410 | * @return A <code>Date</code> parsed from the string. In case of | ||
| 411 | * error, returns null. | ||
| 412 | * @exception NullPointerException if <code>pos</code> is null. | ||
| 413 | */ | ||
| 414 | public Object parseObject(String source, ParsePosition pos) { | ||
| 415 | return parse(source, pos); | ||
| 416 | } | ||
| 417 | |||
| 418 | /** | ||
| 419 | * Constant for full style pattern. | ||
| 420 | */ | ||
| 421 | public static final int FULL = 0; | ||
| 422 | /** | ||
| 423 | * Constant for long style pattern. | ||
| 424 | */ | ||
| 425 | public static final int LONG = 1; | ||
| 426 | /** | ||
| 427 | * Constant for medium style pattern. | ||
| 428 | */ | ||
| 429 | public static final int MEDIUM = 2; | ||
| 430 | /** | ||
| 431 | * Constant for short style pattern. | ||
| 432 | */ | ||
| 433 | public static final int SHORT = 3; | ||
| 434 | /** | ||
| 435 | * Constant for default style pattern. Its value is MEDIUM. | ||
| 436 | */ | ||
| 437 | public static final int DEFAULT = MEDIUM; | ||
| 438 | |||
| 439 | /** | ||
| 440 | * Gets the time formatter with the default formatting style | ||
| 441 | * for the default locale. | ||
| 442 | * @return a time formatter. | ||
| 443 | */ | ||
| 444 | public final static DateFormat getTimeInstance() | ||
| 445 | { | ||
| 446 | return get(DEFAULT, 0, 1, Locale.getDefault(Locale.Category.FORMAT)); | ||
| 447 | } | ||
| 448 | |||
| 449 | /** | ||
| 450 | * Gets the time formatter with the given formatting style | ||
| 451 | * for the default locale. | ||
| 452 | * @param style the given formatting style. For example, | ||
| 453 | * SHORT for "h:mm a" in the US locale. | ||
| 454 | * @return a time formatter. | ||
| 455 | */ | ||
| 456 | public final static DateFormat getTimeInstance(int style) | ||
| 457 | { | ||
| 458 | return get(style, 0, 1, Locale.getDefault(Locale.Category.FORMAT)); | ||
| 459 | } | ||
| 460 | |||
| 461 | /** | ||
| 462 | * Gets the time formatter with the given formatting style | ||
| 463 | * for the given locale. | ||
| 464 | * @param style the given formatting style. For example, | ||
| 465 | * SHORT for "h:mm a" in the US locale. | ||
| 466 | * @param aLocale the given locale. | ||
| 467 | * @return a time formatter. | ||
| 468 | */ | ||
| 469 | public final static DateFormat getTimeInstance(int style, | ||
| 470 | Locale aLocale) | ||
| 471 | { | ||
| 472 | return get(style, 0, 1, aLocale); | ||
| 473 | } | ||
| 474 | |||
| 475 | /** | ||
| 476 | * Gets the date formatter with the default formatting style | ||
| 477 | * for the default locale. | ||
| 478 | * @return a date formatter. | ||
| 479 | */ | ||
| 480 | public final static DateFormat getDateInstance() | ||
| 481 | { | ||
| 482 | return get(0, DEFAULT, 2, Locale.getDefault(Locale.Category.FORMAT)); | ||
| 483 | } | ||
| 484 | |||
| 485 | /** | ||
| 486 | * Gets the date formatter with the given formatting style | ||
| 487 | * for the default locale. | ||
| 488 | * @param style the given formatting style. For example, | ||
| 489 | * SHORT for "M/d/yy" in the US locale. | ||
| 490 | * @return a date formatter. | ||
| 491 | */ | ||
| 492 | public final static DateFormat getDateInstance(int style) | ||
| 493 | { | ||
| 494 | return get(0, style, 2, Locale.getDefault(Locale.Category.FORMAT)); | ||
| 495 | } | ||
| 496 | |||
| 497 | /** | ||
| 498 | * Gets the date formatter with the given formatting style | ||
| 499 | * for the given locale. | ||
| 500 | * @param style the given formatting style. For example, | ||
| 501 | * SHORT for "M/d/yy" in the US locale. | ||
| 502 | * @param aLocale the given locale. | ||
| 503 | * @return a date formatter. | ||
| 504 | */ | ||
| 505 | public final static DateFormat getDateInstance(int style, | ||
| 506 | Locale aLocale) | ||
| 507 | { | ||
| 508 | return get(0, style, 2, aLocale); | ||
| 509 | } | ||
| 510 | |||
| 511 | /** | ||
| 512 | * Gets the date/time formatter with the default formatting style | ||
| 513 | * for the default locale. | ||
| 514 | * @return a date/time formatter. | ||
| 515 | */ | ||
| 516 | public final static DateFormat getDateTimeInstance() | ||
| 517 | { | ||
| 518 | return get(DEFAULT, DEFAULT, 3, Locale.getDefault(Locale.Category.FORMAT)); | ||
| 519 | } | ||
| 520 | |||
| 521 | /** | ||
| 522 | * Gets the date/time formatter with the given date and time | ||
| 523 | * formatting styles for the default locale. | ||
| 524 | * @param dateStyle the given date formatting style. For example, | ||
| 525 | * SHORT for "M/d/yy" in the US locale. | ||
| 526 | * @param timeStyle the given time formatting style. For example, | ||
| 527 | * SHORT for "h:mm a" in the US locale. | ||
| 528 | * @return a date/time formatter. | ||
| 529 | */ | ||
| 530 | public final static DateFormat getDateTimeInstance(int dateStyle, | ||
| 531 | int timeStyle) | ||
| 532 | { | ||
| 533 | return get(timeStyle, dateStyle, 3, Locale.getDefault(Locale.Category.FORMAT)); | ||
| 534 | } | ||
| 535 | |||
| 536 | /** | ||
| 537 | * Gets the date/time formatter with the given formatting styles | ||
| 538 | * for the given locale. | ||
| 539 | * @param dateStyle the given date formatting style. | ||
| 540 | * @param timeStyle the given time formatting style. | ||
| 541 | * @param aLocale the given locale. | ||
| 542 | * @return a date/time formatter. | ||
| 543 | */ | ||
| 544 | public final static DateFormat | ||
| 545 | getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale) | ||
| 546 | { | ||
| 547 | return get(timeStyle, dateStyle, 3, aLocale); | ||
| 548 | } | ||
| 549 | |||
| 550 | /** | ||
| 551 | * Get a default date/time formatter that uses the SHORT style for both the | ||
| 552 | * date and the time. | ||
| 553 | */ | ||
| 554 | public final static DateFormat getInstance() { | ||
| 555 | return getDateTimeInstance(SHORT, SHORT); | ||
| 556 | } | ||
| 557 | |||
| 558 | /** | ||
| 559 | * Returns an array of all locales for which the | ||
| 560 | * <code>get*Instance</code> methods of this class can return | ||
| 561 | * localized instances. | ||
| 562 | * The returned array represents the union of locales supported by the Java | ||
| 563 | * runtime and by installed | ||
| 564 | * {@link java.text.spi.DateFormatProvider DateFormatProvider} implementations. | ||
| 565 | * It must contain at least a <code>Locale</code> instance equal to | ||
| 566 | * {@link java.util.Locale#US Locale.US}. | ||
| 567 | * | ||
| 568 | * @return An array of locales for which localized | ||
| 569 | * <code>DateFormat</code> instances are available. | ||
| 570 | */ | ||
| 571 | public static Locale[] getAvailableLocales() | ||
| 572 | { | ||
| 573 | LocaleServiceProviderPool pool = | ||
| 574 | LocaleServiceProviderPool.getPool(DateFormatProvider.class); | ||
| 575 | return pool.getAvailableLocales(); | ||
| 576 | } | ||
| 577 | |||
| 578 | /** | ||
| 579 | * Set the calendar to be used by this date format. Initially, the default | ||
| 580 | * calendar for the specified or default locale is used. | ||
| 581 | * | ||
| 582 | * <p>Any {@link java.util.TimeZone TimeZone} and {@linkplain | ||
| 583 | * #isLenient() leniency} values that have previously been set are | ||
| 584 | * overwritten by {@code newCalendar}'s values. | ||
| 585 | * | ||
| 586 | * @param newCalendar the new {@code Calendar} to be used by the date format | ||
| 587 | */ | ||
| 588 | public void setCalendar(Calendar newCalendar) | ||
| 589 | { | ||
| 590 | this.calendar = newCalendar; | ||
| 591 | } | ||
| 592 | |||
| 593 | /** | ||
| 594 | * Gets the calendar associated with this date/time formatter. | ||
| 595 | * | ||
| 596 | * @return the calendar associated with this date/time formatter. | ||
| 597 | */ | ||
| 598 | public Calendar getCalendar() | ||
| 599 | { | ||
| 600 | return calendar; | ||
| 601 | } | ||
| 602 | |||
| 603 | /** | ||
| 604 | * Allows you to set the number formatter. | ||
| 605 | * @param newNumberFormat the given new NumberFormat. | ||
| 606 | */ | ||
| 607 | public void setNumberFormat(NumberFormat newNumberFormat) | ||
| 608 | { | ||
| 609 | this.numberFormat = newNumberFormat; | ||
| 610 | } | ||
| 611 | |||
| 612 | /** | ||
| 613 | * Gets the number formatter which this date/time formatter uses to | ||
| 614 | * format and parse a time. | ||
| 615 | * @return the number formatter which this date/time formatter uses. | ||
| 616 | */ | ||
| 617 | public NumberFormat getNumberFormat() | ||
| 618 | { | ||
| 619 | return numberFormat; | ||
| 620 | } | ||
| 621 | |||
| 622 | /** | ||
| 623 | * Sets the time zone for the calendar of this {@code DateFormat} object. | ||
| 624 | * This method is equivalent to the following call. | ||
| 625 | * <blockquote><pre> | ||
| 626 | * getCalendar().setTimeZone(zone) | ||
| 627 | * </pre></blockquote> | ||
| 628 | * | ||
| 629 | * <p>The {@code TimeZone} set by this method is overwritten by a | ||
| 630 | * {@link #setCalendar(java.util.Calendar) setCalendar} call. | ||
| 631 | * | ||
| 632 | * <p>The {@code TimeZone} set by this method may be overwritten as | ||
| 633 | * a result of a call to the parse method. | ||
| 634 | * | ||
| 635 | * @param zone the given new time zone. | ||
| 636 | */ | ||
| 637 | public void setTimeZone(TimeZone zone) | ||
| 638 | { | ||
| 639 | calendar.setTimeZone(zone); | ||
| 640 | } | ||
| 641 | |||
| 642 | /** | ||
| 643 | * Gets the time zone. | ||
| 644 | * This method is equivalent to the following call. | ||
| 645 | * <blockquote><pre> | ||
| 646 | * getCalendar().getTimeZone() | ||
| 647 | * </pre></blockquote> | ||
| 648 | * | ||
| 649 | * @return the time zone associated with the calendar of DateFormat. | ||
| 650 | */ | ||
| 651 | public TimeZone getTimeZone() | ||
| 652 | { | ||
| 653 | return calendar.getTimeZone(); | ||
| 654 | } | ||
| 655 | |||
| 656 | /** | ||
| 657 | * Specify whether or not date/time parsing is to be lenient. With | ||
| 658 | * lenient parsing, the parser may use heuristics to interpret inputs that | ||
| 659 | * do not precisely match this object's format. With strict parsing, | ||
| 660 | * inputs must match this object's format. | ||
| 661 | * | ||
| 662 | * <p>This method is equivalent to the following call. | ||
| 663 | * <blockquote><pre> | ||
| 664 | * getCalendar().setLenient(lenient) | ||
| 665 | * </pre></blockquote> | ||
| 666 | * | ||
| 667 | * <p>This leniency value is overwritten by a call to {@link | ||
| 668 | * #setCalendar(java.util.Calendar) setCalendar()}. | ||
| 669 | * | ||
| 670 | * @param lenient when {@code true}, parsing is lenient | ||
| 671 | * @see java.util.Calendar#setLenient(boolean) | ||
| 672 | */ | ||
| 673 | public void setLenient(boolean lenient) | ||
| 674 | { | ||
| 675 | calendar.setLenient(lenient); | ||
| 676 | } | ||
| 677 | |||
| 678 | /** | ||
| 679 | * Tell whether date/time parsing is to be lenient. | ||
| 680 | * This method is equivalent to the following call. | ||
| 681 | * <blockquote><pre> | ||
| 682 | * getCalendar().isLenient() | ||
| 683 | * </pre></blockquote> | ||
| 684 | * | ||
| 685 | * @return {@code true} if the {@link #calendar} is lenient; | ||
| 686 | * {@code false} otherwise. | ||
| 687 | * @see java.util.Calendar#isLenient() | ||
| 688 | */ | ||
| 689 | public boolean isLenient() | ||
| 690 | { | ||
| 691 | return calendar.isLenient(); | ||
| 692 | } | ||
| 693 | |||
| 694 | /** | ||
| 695 | * Overrides hashCode | ||
| 696 | */ | ||
| 697 | public int hashCode() { | ||
| 698 | return numberFormat.hashCode(); | ||
| 699 | // just enough fields for a reasonable distribution | ||
| 700 | } | ||
| 701 | |||
| 702 | /** | ||
| 703 | * Overrides equals | ||
| 704 | */ | ||
| 705 | public boolean equals(Object obj) { | ||
| 706 | if (this == obj) return true; | ||
| 707 | if (obj == null || getClass() != obj.getClass()) return false; | ||
| 708 | DateFormat other = (DateFormat) obj; | ||
| 709 | return (// calendar.equivalentTo(other.calendar) // THIS API DOESN'T EXIST YET! | ||
| 710 | calendar.getFirstDayOfWeek() == other.calendar.getFirstDayOfWeek() && | ||
| 711 | calendar.getMinimalDaysInFirstWeek() == other.calendar.getMinimalDaysInFirstWeek() && | ||
| 712 | calendar.isLenient() == other.calendar.isLenient() && | ||
| 713 | calendar.getTimeZone().equals(other.calendar.getTimeZone()) && | ||
| 714 | numberFormat.equals(other.numberFormat)); | ||
| 715 | } | ||
| 716 | |||
| 717 | /** | ||
| 718 | * Overrides Cloneable | ||
| 719 | */ | ||
| 720 | public Object clone() | ||
| 721 | { | ||
| 722 | DateFormat other = (DateFormat) super.clone(); | ||
| 723 | other.calendar = (Calendar) calendar.clone(); | ||
| 724 | other.numberFormat = (NumberFormat) numberFormat.clone(); | ||
| 725 | return other; | ||
| 726 | } | ||
| 727 | |||
| 728 | /** | ||
| 729 | * Creates a DateFormat with the given time and/or date style in the given | ||
| 730 | * locale. | ||
| 731 | * @param timeStyle a value from 0 to 3 indicating the time format, | ||
| 732 | * ignored if flags is 2 | ||
| 733 | * @param dateStyle a value from 0 to 3 indicating the time format, | ||
| 734 | * ignored if flags is 1 | ||
| 735 | * @param flags either 1 for a time format, 2 for a date format, | ||
| 736 | * or 3 for a date/time format | ||
| 737 | * @param loc the locale for the format | ||
| 738 | */ | ||
| 739 | private static DateFormat get(int timeStyle, int dateStyle, | ||
| 740 | int flags, Locale loc) { | ||
| 741 | if ((flags & 1) != 0) { | ||
| 742 | if (timeStyle < 0 || timeStyle > 3) { | ||
| 743 | throw new IllegalArgumentException("Illegal time style " + timeStyle); | ||
| 744 | } | ||
| 745 | } else { | ||
| 746 | timeStyle = -1; | ||
| 747 | } | ||
| 748 | if ((flags & 2) != 0) { | ||
| 749 | if (dateStyle < 0 || dateStyle > 3) { | ||
| 750 | throw new IllegalArgumentException("Illegal date style " + dateStyle); | ||
| 751 | } | ||
| 752 | } else { | ||
| 753 | dateStyle = -1; | ||
| 754 | } | ||
| 755 | try { | ||
| 756 | // Check whether a provider can provide an implementation that's closer | ||
| 757 | // to the requested locale than what the Java runtime itself can provide. | ||
| 758 | LocaleServiceProviderPool pool = | ||
| 759 | LocaleServiceProviderPool.getPool(DateFormatProvider.class); | ||
| 760 | if (pool.hasProviders()) { | ||
| 761 | DateFormat providersInstance = pool.getLocalizedObject( | ||
| 762 | DateFormatGetter.INSTANCE, | ||
| 763 | loc, | ||
| 764 | timeStyle, | ||
| 765 | dateStyle, | ||
| 766 | flags); | ||
| 767 | if (providersInstance != null) { | ||
| 768 | return providersInstance; | ||
| 769 | } | ||
| 770 | } | ||
| 771 | |||
| 772 | return new SimpleDateFormat(timeStyle, dateStyle, loc); | ||
| 773 | } catch (MissingResourceException e) { | ||
| 774 | return new SimpleDateFormat("M/d/yy h:mm a"); | ||
| 775 | } | ||
| 776 | } | ||
| 777 | |||
| 778 | /** | ||
| 779 | * Create a new date format. | ||
| 780 | */ | ||
| 781 | protected DateFormat() {} | ||
| 782 | |||
| 783 | /** | ||
| 784 | * Defines constants that are used as attribute keys in the | ||
| 785 | * <code>AttributedCharacterIterator</code> returned | ||
| 786 | * from <code>DateFormat.formatToCharacterIterator</code> and as | ||
| 787 | * field identifiers in <code>FieldPosition</code>. | ||
| 788 | * <p> | ||
| 789 | * The class also provides two methods to map | ||
| 790 | * between its constants and the corresponding Calendar constants. | ||
| 791 | * | ||
| 792 | * @since 1.4 | ||
| 793 | * @see java.util.Calendar | ||
| 794 | */ | ||
| 795 | public static class Field extends Format.Field { | ||
| 796 | |||
| 797 | // Proclaim serial compatibility with 1.4 FCS | ||
| 798 | private static final long serialVersionUID = 7441350119349544720L; | ||
| 799 | |||
| 800 | // table of all instances in this class, used by readResolve | ||
| 801 | private static final Map instanceMap = new HashMap(18); | ||
| 802 | // Maps from Calendar constant (such as Calendar.ERA) to Field | ||
| 803 | // constant (such as Field.ERA). | ||
| 804 | private static final Field[] calendarToFieldMapping = | ||
| 805 | new Field[Calendar.FIELD_COUNT]; | ||
| 806 | |||
| 807 | /** Calendar field. */ | ||
| 808 | private int calendarField; | ||
| 809 | |||
| 810 | /** | ||
| 811 | * Returns the <code>Field</code> constant that corresponds to | ||
| 812 | * the <code>Calendar</code> constant <code>calendarField</code>. | ||
| 813 | * If there is no direct mapping between the <code>Calendar</code> | ||
| 814 | * constant and a <code>Field</code>, null is returned. | ||
| 815 | * | ||
| 816 | * @throws IllegalArgumentException if <code>calendarField</code> is | ||
| 817 | * not the value of a <code>Calendar</code> field constant. | ||
| 818 | * @param calendarField Calendar field constant | ||
| 819 | * @return Field instance representing calendarField. | ||
| 820 | * @see java.util.Calendar | ||
| 821 | */ | ||
| 822 | public static Field ofCalendarField(int calendarField) { | ||
| 823 | if (calendarField < 0 || calendarField >= | ||
| 824 | calendarToFieldMapping.length) { | ||
| 825 | throw new IllegalArgumentException("Unknown Calendar constant " | ||
| 826 | + calendarField); | ||
| 827 | } | ||
| 828 | return calendarToFieldMapping[calendarField]; | ||
| 829 | } | ||
| 830 | |||
| 831 | /** | ||
| 832 | * Creates a <code>Field</code>. | ||
| 833 | * | ||
| 834 | * @param name the name of the <code>Field</code> | ||
| 835 | * @param calendarField the <code>Calendar</code> constant this | ||
| 836 | * <code>Field</code> corresponds to; any value, even one | ||
| 837 | * outside the range of legal <code>Calendar</code> values may | ||
| 838 | * be used, but <code>-1</code> should be used for values | ||
| 839 | * that don't correspond to legal <code>Calendar</code> values | ||
| 840 | */ | ||
| 841 | protected Field(String name, int calendarField) { | ||
| 842 | super(name); | ||
| 843 | this.calendarField = calendarField; | ||
| 844 | if (this.getClass() == DateFormat.Field.class) { | ||
| 845 | instanceMap.put(name, this); | ||
| 846 | if (calendarField >= 0) { | ||
| 847 | // assert(calendarField < Calendar.FIELD_COUNT); | ||
| 848 | calendarToFieldMapping[calendarField] = this; | ||
| 849 | } | ||
| 850 | } | ||
| 851 | } | ||
| 852 | |||
| 853 | /** | ||
| 854 | * Returns the <code>Calendar</code> field associated with this | ||
| 855 | * attribute. For example, if this represents the hours field of | ||
| 856 | * a <code>Calendar</code>, this would return | ||
| 857 | * <code>Calendar.HOUR</code>. If there is no corresponding | ||
| 858 | * <code>Calendar</code> constant, this will return -1. | ||
| 859 | * | ||
| 860 | * @return Calendar constant for this field | ||
| 861 | * @see java.util.Calendar | ||
| 862 | */ | ||
| 863 | public int getCalendarField() { | ||
| 864 | return calendarField; | ||
| 865 | } | ||
| 866 | |||
| 867 | /** | ||
| 868 | * Resolves instances being deserialized to the predefined constants. | ||
| 869 | * | ||
| 870 | * @throws InvalidObjectException if the constant could not be | ||
| 871 | * resolved. | ||
| 872 | * @return resolved DateFormat.Field constant | ||
| 873 | */ | ||
| 874 | protected Object readResolve() throws InvalidObjectException { | ||
| 875 | if (this.getClass() != DateFormat.Field.class) { | ||
| 876 | throw new InvalidObjectException("subclass didn't correctly implement readResolve"); | ||
| 877 | } | ||
| 878 | |||
| 879 | Object instance = instanceMap.get(getName()); | ||
| 880 | if (instance != null) { | ||
| 881 | return instance; | ||
| 882 | } else { | ||
| 883 | throw new InvalidObjectException("unknown attribute name"); | ||
| 884 | } | ||
| 885 | } | ||
| 886 | |||
| 887 | // | ||
| 888 | // The constants | ||
| 889 | // | ||
| 890 | |||
| 891 | /** | ||
| 892 | * Constant identifying the era field. | ||
| 893 | */ | ||
| 894 | public final static Field ERA = new Field("era", Calendar.ERA); | ||
| 895 | |||
| 896 | /** | ||
| 897 | * Constant identifying the year field. | ||
| 898 | */ | ||
| 899 | public final static Field YEAR = new Field("year", Calendar.YEAR); | ||
| 900 | |||
| 901 | /** | ||
| 902 | * Constant identifying the month field. | ||
| 903 | */ | ||
| 904 | public final static Field MONTH = new Field("month", Calendar.MONTH); | ||
| 905 | |||
| 906 | /** | ||
| 907 | * Constant identifying the day of month field. | ||
| 908 | */ | ||
| 909 | public final static Field DAY_OF_MONTH = new | ||
| 910 | Field("day of month", Calendar.DAY_OF_MONTH); | ||
| 911 | |||
| 912 | /** | ||
| 913 | * Constant identifying the hour of day field, where the legal values | ||
| 914 | * are 1 to 24. | ||
| 915 | */ | ||
| 916 | public final static Field HOUR_OF_DAY1 = new Field("hour of day 1",-1); | ||
| 917 | |||
| 918 | /** | ||
| 919 | * Constant identifying the hour of day field, where the legal values | ||
| 920 | * are 0 to 23. | ||
| 921 | */ | ||
| 922 | public final static Field HOUR_OF_DAY0 = new | ||
| 923 | Field("hour of day", Calendar.HOUR_OF_DAY); | ||
| 924 | |||
| 925 | /** | ||
| 926 | * Constant identifying the minute field. | ||
| 927 | */ | ||
| 928 | public final static Field MINUTE =new Field("minute", Calendar.MINUTE); | ||
| 929 | |||
| 930 | /** | ||
| 931 | * Constant identifying the second field. | ||
| 932 | */ | ||
| 933 | public final static Field SECOND =new Field("second", Calendar.SECOND); | ||
| 934 | |||
| 935 | /** | ||
| 936 | * Constant identifying the millisecond field. | ||
| 937 | */ | ||
| 938 | public final static Field MILLISECOND = new | ||
| 939 | Field("millisecond", Calendar.MILLISECOND); | ||
| 940 | |||
| 941 | /** | ||
| 942 | * Constant identifying the day of week field. | ||
| 943 | */ | ||
| 944 | public final static Field DAY_OF_WEEK = new | ||
| 945 | Field("day of week", Calendar.DAY_OF_WEEK); | ||
| 946 | |||
| 947 | /** | ||
| 948 | * Constant identifying the day of year field. | ||
| 949 | */ | ||
| 950 | public final static Field DAY_OF_YEAR = new | ||
| 951 | Field("day of year", Calendar.DAY_OF_YEAR); | ||
| 952 | |||
| 953 | /** | ||
| 954 | * Constant identifying the day of week field. | ||
| 955 | */ | ||
| 956 | public final static Field DAY_OF_WEEK_IN_MONTH = | ||
| 957 | new Field("day of week in month", | ||
| 958 | Calendar.DAY_OF_WEEK_IN_MONTH); | ||
| 959 | |||
| 960 | /** | ||
| 961 | * Constant identifying the week of year field. | ||
| 962 | */ | ||
| 963 | public final static Field WEEK_OF_YEAR = new | ||
| 964 | Field("week of year", Calendar.WEEK_OF_YEAR); | ||
| 965 | |||
| 966 | /** | ||
| 967 | * Constant identifying the week of month field. | ||
| 968 | */ | ||
| 969 | public final static Field WEEK_OF_MONTH = new | ||
| 970 | Field("week of month", Calendar.WEEK_OF_MONTH); | ||
| 971 | |||
| 972 | /** | ||
| 973 | * Constant identifying the time of day indicator | ||
| 974 | * (e.g. "a.m." or "p.m.") field. | ||
| 975 | */ | ||
| 976 | public final static Field AM_PM = new | ||
| 977 | Field("am pm", Calendar.AM_PM); | ||
| 978 | |||
| 979 | /** | ||
| 980 | * Constant identifying the hour field, where the legal values are | ||
| 981 | * 1 to 12. | ||
| 982 | */ | ||
| 983 | public final static Field HOUR1 = new Field("hour 1", -1); | ||
| 984 | |||
| 985 | /** | ||
| 986 | * Constant identifying the hour field, where the legal values are | ||
| 987 | * 0 to 11. | ||
| 988 | */ | ||
| 989 | public final static Field HOUR0 = new | ||
| 990 | Field("hour", Calendar.HOUR); | ||
| 991 | |||
| 992 | /** | ||
| 993 | * Constant identifying the time zone field. | ||
| 994 | */ | ||
| 995 | public final static Field TIME_ZONE = new Field("time zone", -1); | ||
| 996 | } | ||
| 997 | |||
| 998 | /** | ||
| 999 | * Obtains a DateFormat instance from a DateFormatProvider | ||
| 1000 | * implementation. | ||
| 1001 | */ | ||
| 1002 | private static class DateFormatGetter | ||
| 1003 | implements LocaleServiceProviderPool.LocalizedObjectGetter<DateFormatProvider, DateFormat> { | ||
| 1004 | private static final DateFormatGetter INSTANCE = new DateFormatGetter(); | ||
| 1005 | |||
| 1006 | public DateFormat getObject(DateFormatProvider dateFormatProvider, | ||
| 1007 | Locale locale, | ||
| 1008 | String key, | ||
| 1009 | Object... params) { | ||
| 1010 | assert params.length == 3; | ||
| 1011 | |||
| 1012 | int timeStyle = (Integer)params[0]; | ||
| 1013 | int dateStyle = (Integer)params[1]; | ||
| 1014 | int flags = (Integer)params[2]; | ||
| 1015 | |||
| 1016 | switch (flags) { | ||
| 1017 | case 1: | ||
| 1018 | return dateFormatProvider.getTimeInstance(timeStyle, locale); | ||
| 1019 | case 2: | ||
| 1020 | return dateFormatProvider.getDateInstance(dateStyle, locale); | ||
| 1021 | case 3: | ||
| 1022 | return dateFormatProvider.getDateTimeInstance(dateStyle, timeStyle, locale); | ||
| 1023 | default: | ||
| 1024 | assert false : "should not happen"; | ||
| 1025 | } | ||
| 1026 | |||
| 1027 | return null; | ||
| 1028 | } | ||
| 1029 | } | ||
| 1030 | } | ||
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,794 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.io.IOException; | ||
| 42 | import java.io.ObjectOutputStream; | ||
| 43 | import java.io.Serializable; | ||
| 44 | import java.lang.ref.SoftReference; | ||
| 45 | import java.text.spi.DateFormatSymbolsProvider; | ||
| 46 | import java.util.Arrays; | ||
| 47 | import java.util.List; | ||
| 48 | import java.util.Locale; | ||
| 49 | import java.util.ResourceBundle; | ||
| 50 | import java.util.TimeZone; | ||
| 51 | import java.util.concurrent.ConcurrentHashMap; | ||
| 52 | import java.util.concurrent.ConcurrentMap; | ||
| 53 | import java.util.spi.LocaleServiceProvider; | ||
| 54 | import sun.util.LocaleServiceProviderPool; | ||
| 55 | import sun.util.TimeZoneNameUtility; | ||
| 56 | import sun.util.calendar.ZoneInfo; | ||
| 57 | import sun.util.resources.LocaleData; | ||
| 58 | |||
| 59 | /** | ||
| 60 | * <code>DateFormatSymbols</code> is a public class for encapsulating | ||
| 61 | * localizable date-time formatting data, such as the names of the | ||
| 62 | * months, the names of the days of the week, and the time zone data. | ||
| 63 | * <code>DateFormat</code> and <code>SimpleDateFormat</code> both use | ||
| 64 | * <code>DateFormatSymbols</code> to encapsulate this information. | ||
| 65 | * | ||
| 66 | * <p> | ||
| 67 | * Typically you shouldn't use <code>DateFormatSymbols</code> directly. | ||
| 68 | * Rather, you are encouraged to create a date-time formatter with the | ||
| 69 | * <code>DateFormat</code> class's factory methods: <code>getTimeInstance</code>, | ||
| 70 | * <code>getDateInstance</code>, or <code>getDateTimeInstance</code>. | ||
| 71 | * These methods automatically create a <code>DateFormatSymbols</code> for | ||
| 72 | * the formatter so that you don't have to. After the | ||
| 73 | * formatter is created, you may modify its format pattern using the | ||
| 74 | * <code>setPattern</code> method. For more information about | ||
| 75 | * creating formatters using <code>DateFormat</code>'s factory methods, | ||
| 76 | * see {@link DateFormat}. | ||
| 77 | * | ||
| 78 | * <p> | ||
| 79 | * If you decide to create a date-time formatter with a specific | ||
| 80 | * format pattern for a specific locale, you can do so with: | ||
| 81 | * <blockquote> | ||
| 82 | * <pre> | ||
| 83 | * new SimpleDateFormat(aPattern, DateFormatSymbols.getInstance(aLocale)). | ||
| 84 | * </pre> | ||
| 85 | * </blockquote> | ||
| 86 | * | ||
| 87 | * <p> | ||
| 88 | * <code>DateFormatSymbols</code> objects are cloneable. When you obtain | ||
| 89 | * a <code>DateFormatSymbols</code> object, feel free to modify the | ||
| 90 | * date-time formatting data. For instance, you can replace the localized | ||
| 91 | * date-time format pattern characters with the ones that you feel easy | ||
| 92 | * to remember. Or you can change the representative cities | ||
| 93 | * to your favorite ones. | ||
| 94 | * | ||
| 95 | * <p> | ||
| 96 | * New <code>DateFormatSymbols</code> subclasses may be added to support | ||
| 97 | * <code>SimpleDateFormat</code> for date-time formatting for additional locales. | ||
| 98 | |||
| 99 | * @see DateFormat | ||
| 100 | * @see SimpleDateFormat | ||
| 101 | * @see java.util.SimpleTimeZone | ||
| 102 | * @author Chen-Lieh Huang | ||
| 103 | */ | ||
| 104 | public class DateFormatSymbols implements Serializable, Cloneable { | ||
| 105 | |||
| 106 | /** | ||
| 107 | * Construct a DateFormatSymbols object by loading format data from | ||
| 108 | * resources for the default locale. This constructor can only | ||
| 109 | * construct instances for the locales supported by the Java | ||
| 110 | * runtime environment, not for those supported by installed | ||
| 111 | * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} | ||
| 112 | * implementations. For full locale coverage, use the | ||
| 113 | * {@link #getInstance(Locale) getInstance} method. | ||
| 114 | * | ||
| 115 | * @see #getInstance() | ||
| 116 | * @exception java.util.MissingResourceException | ||
| 117 | * if the resources for the default locale cannot be | ||
| 118 | * found or cannot be loaded. | ||
| 119 | */ | ||
| 120 | public DateFormatSymbols() | ||
| 121 | { | ||
| 122 | initializeData(Locale.getDefault(Locale.Category.FORMAT)); | ||
| 123 | } | ||
| 124 | |||
| 125 | /** | ||
| 126 | * Construct a DateFormatSymbols object by loading format data from | ||
| 127 | * resources for the given locale. This constructor can only | ||
| 128 | * construct instances for the locales supported by the Java | ||
| 129 | * runtime environment, not for those supported by installed | ||
| 130 | * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} | ||
| 131 | * implementations. For full locale coverage, use the | ||
| 132 | * {@link #getInstance(Locale) getInstance} method. | ||
| 133 | * | ||
| 134 | * @see #getInstance(Locale) | ||
| 135 | * @exception java.util.MissingResourceException | ||
| 136 | * if the resources for the specified locale cannot be | ||
| 137 | * found or cannot be loaded. | ||
| 138 | */ | ||
| 139 | public DateFormatSymbols(Locale locale) | ||
| 140 | { | ||
| 141 | initializeData(locale); | ||
| 142 | } | ||
| 143 | |||
| 144 | /** | ||
| 145 | * Era strings. For example: "AD" and "BC". An array of 2 strings, | ||
| 146 | * indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>. | ||
| 147 | * @serial | ||
| 148 | */ | ||
| 149 | String eras[] = null; | ||
| 150 | |||
| 151 | /** | ||
| 152 | * Month strings. For example: "January", "February", etc. An array | ||
| 153 | * of 13 strings (some calendars have 13 months), indexed by | ||
| 154 | * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc. | ||
| 155 | * @serial | ||
| 156 | */ | ||
| 157 | String months[] = null; | ||
| 158 | |||
| 159 | /** | ||
| 160 | * Short month strings. For example: "Jan", "Feb", etc. An array of | ||
| 161 | * 13 strings (some calendars have 13 months), indexed by | ||
| 162 | * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc. | ||
| 163 | |||
| 164 | * @serial | ||
| 165 | */ | ||
| 166 | String shortMonths[] = null; | ||
| 167 | |||
| 168 | /** | ||
| 169 | * Weekday strings. For example: "Sunday", "Monday", etc. An array | ||
| 170 | * of 8 strings, indexed by <code>Calendar.SUNDAY</code>, | ||
| 171 | * <code>Calendar.MONDAY</code>, etc. | ||
| 172 | * The element <code>weekdays[0]</code> is ignored. | ||
| 173 | * @serial | ||
| 174 | */ | ||
| 175 | String weekdays[] = null; | ||
| 176 | |||
| 177 | /** | ||
| 178 | * Short weekday strings. For example: "Sun", "Mon", etc. An array | ||
| 179 | * of 8 strings, indexed by <code>Calendar.SUNDAY</code>, | ||
| 180 | * <code>Calendar.MONDAY</code>, etc. | ||
| 181 | * The element <code>shortWeekdays[0]</code> is ignored. | ||
| 182 | * @serial | ||
| 183 | */ | ||
| 184 | String shortWeekdays[] = null; | ||
| 185 | |||
| 186 | /** | ||
| 187 | * AM and PM strings. For example: "AM" and "PM". An array of | ||
| 188 | * 2 strings, indexed by <code>Calendar.AM</code> and | ||
| 189 | * <code>Calendar.PM</code>. | ||
| 190 | * @serial | ||
| 191 | */ | ||
| 192 | String ampms[] = null; | ||
| 193 | |||
| 194 | /** | ||
| 195 | * Localized names of time zones in this locale. This is a | ||
| 196 | * two-dimensional array of strings of size <em>n</em> by <em>m</em>, | ||
| 197 | * where <em>m</em> is at least 5. Each of the <em>n</em> rows is an | ||
| 198 | * entry containing the localized names for a single <code>TimeZone</code>. | ||
| 199 | * Each such row contains (with <code>i</code> ranging from | ||
| 200 | * 0..<em>n</em>-1): | ||
| 201 | * <ul> | ||
| 202 | * <li><code>zoneStrings[i][0]</code> - time zone ID</li> | ||
| 203 | * <li><code>zoneStrings[i][1]</code> - long name of zone in standard | ||
| 204 | * time</li> | ||
| 205 | * <li><code>zoneStrings[i][2]</code> - short name of zone in | ||
| 206 | * standard time</li> | ||
| 207 | * <li><code>zoneStrings[i][3]</code> - long name of zone in daylight | ||
| 208 | * saving time</li> | ||
| 209 | * <li><code>zoneStrings[i][4]</code> - short name of zone in daylight | ||
| 210 | * saving time</li> | ||
| 211 | * </ul> | ||
| 212 | * The zone ID is <em>not</em> localized; it's one of the valid IDs of | ||
| 213 | * the {@link java.util.TimeZone TimeZone} class that are not | ||
| 214 | * <a href="../java/util/TimeZone.html#CustomID">custom IDs</a>. | ||
| 215 | * All other entries are localized names. | ||
| 216 | * @see java.util.TimeZone | ||
| 217 | * @serial | ||
| 218 | */ | ||
| 219 | String zoneStrings[][] = null; | ||
| 220 | |||
| 221 | /** | ||
| 222 | * Indicates that zoneStrings is set externally with setZoneStrings() method. | ||
| 223 | */ | ||
| 224 | transient boolean isZoneStringsSet = false; | ||
| 225 | |||
| 226 | /** | ||
| 227 | * Unlocalized date-time pattern characters. For example: 'y', 'd', etc. | ||
| 228 | * All locales use the same these unlocalized pattern characters. | ||
| 229 | */ | ||
| 230 | static final String patternChars = "GyMdkHmsSEDFwWahKzZYuX"; | ||
| 231 | |||
| 232 | static final int PATTERN_ERA = 0; // G | ||
| 233 | static final int PATTERN_YEAR = 1; // y | ||
| 234 | static final int PATTERN_MONTH = 2; // M | ||
| 235 | static final int PATTERN_DAY_OF_MONTH = 3; // d | ||
| 236 | static final int PATTERN_HOUR_OF_DAY1 = 4; // k | ||
| 237 | static final int PATTERN_HOUR_OF_DAY0 = 5; // H | ||
| 238 | static final int PATTERN_MINUTE = 6; // m | ||
| 239 | static final int PATTERN_SECOND = 7; // s | ||
| 240 | static final int PATTERN_MILLISECOND = 8; // S | ||
| 241 | static final int PATTERN_DAY_OF_WEEK = 9; // E | ||
| 242 | static final int PATTERN_DAY_OF_YEAR = 10; // D | ||
| 243 | static final int PATTERN_DAY_OF_WEEK_IN_MONTH = 11; // F | ||
| 244 | static final int PATTERN_WEEK_OF_YEAR = 12; // w | ||
| 245 | static final int PATTERN_WEEK_OF_MONTH = 13; // W | ||
| 246 | static final int PATTERN_AM_PM = 14; // a | ||
| 247 | static final int PATTERN_HOUR1 = 15; // h | ||
| 248 | static final int PATTERN_HOUR0 = 16; // K | ||
| 249 | static final int PATTERN_ZONE_NAME = 17; // z | ||
| 250 | static final int PATTERN_ZONE_VALUE = 18; // Z | ||
| 251 | static final int PATTERN_WEEK_YEAR = 19; // Y | ||
| 252 | static final int PATTERN_ISO_DAY_OF_WEEK = 20; // u | ||
| 253 | static final int PATTERN_ISO_ZONE = 21; // X | ||
| 254 | |||
| 255 | /** | ||
| 256 | * Localized date-time pattern characters. For example, a locale may | ||
| 257 | * wish to use 'u' rather than 'y' to represent years in its date format | ||
| 258 | * pattern strings. | ||
| 259 | * This string must be exactly 18 characters long, with the index of | ||
| 260 | * the characters described by <code>DateFormat.ERA_FIELD</code>, | ||
| 261 | * <code>DateFormat.YEAR_FIELD</code>, etc. Thus, if the string were | ||
| 262 | * "Xz...", then localized patterns would use 'X' for era and 'z' for year. | ||
| 263 | * @serial | ||
| 264 | */ | ||
| 265 | String localPatternChars = null; | ||
| 266 | |||
| 267 | /** | ||
| 268 | * The locale which is used for initializing this DateFormatSymbols object. | ||
| 269 | * | ||
| 270 | * @since 1.6 | ||
| 271 | * @serial | ||
| 272 | */ | ||
| 273 | Locale locale = null; | ||
| 274 | |||
| 275 | /* use serialVersionUID from JDK 1.1.4 for interoperability */ | ||
| 276 | static final long serialVersionUID = -5987973545549424702L; | ||
| 277 | |||
| 278 | /** | ||
| 279 | * Returns an array of all locales for which the | ||
| 280 | * <code>getInstance</code> methods of this class can return | ||
| 281 | * localized instances. | ||
| 282 | * The returned array represents the union of locales supported by the | ||
| 283 | * Java runtime and by installed | ||
| 284 | * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} | ||
| 285 | * implementations. It must contain at least a <code>Locale</code> | ||
| 286 | * instance equal to {@link java.util.Locale#US Locale.US}. | ||
| 287 | * | ||
| 288 | * @return An array of locales for which localized | ||
| 289 | * <code>DateFormatSymbols</code> instances are available. | ||
| 290 | * @since 1.6 | ||
| 291 | */ | ||
| 292 | public static Locale[] getAvailableLocales() { | ||
| 293 | LocaleServiceProviderPool pool= | ||
| 294 | LocaleServiceProviderPool.getPool(DateFormatSymbolsProvider.class); | ||
| 295 | return pool.getAvailableLocales(); | ||
| 296 | } | ||
| 297 | |||
| 298 | /** | ||
| 299 | * Gets the <code>DateFormatSymbols</code> instance for the default | ||
| 300 | * locale. This method provides access to <code>DateFormatSymbols</code> | ||
| 301 | * instances for locales supported by the Java runtime itself as well | ||
| 302 | * as for those supported by installed | ||
| 303 | * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} | ||
| 304 | * implementations. | ||
| 305 | * @return a <code>DateFormatSymbols</code> instance. | ||
| 306 | * @since 1.6 | ||
| 307 | */ | ||
| 308 | public static final DateFormatSymbols getInstance() { | ||
| 309 | return getInstance(Locale.getDefault(Locale.Category.FORMAT)); | ||
| 310 | } | ||
| 311 | |||
| 312 | /** | ||
| 313 | * Gets the <code>DateFormatSymbols</code> instance for the specified | ||
| 314 | * locale. This method provides access to <code>DateFormatSymbols</code> | ||
| 315 | * instances for locales supported by the Java runtime itself as well | ||
| 316 | * as for those supported by installed | ||
| 317 | * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} | ||
| 318 | * implementations. | ||
| 319 | * @param locale the given locale. | ||
| 320 | * @return a <code>DateFormatSymbols</code> instance. | ||
| 321 | * @exception NullPointerException if <code>locale</code> is null | ||
| 322 | * @since 1.6 | ||
| 323 | */ | ||
| 324 | public static final DateFormatSymbols getInstance(Locale locale) { | ||
| 325 | DateFormatSymbols dfs = getProviderInstance(locale); | ||
| 326 | if (dfs != null) { | ||
| 327 | return dfs; | ||
| 328 | } | ||
| 329 | return (DateFormatSymbols) getCachedInstance(locale).clone(); | ||
| 330 | } | ||
| 331 | |||
| 332 | /** | ||
| 333 | * Returns a DateFormatSymbols provided by a provider or found in | ||
| 334 | * the cache. Note that this method returns a cached instance, | ||
| 335 | * not its clone. Therefore, the instance should never be given to | ||
| 336 | * an application. | ||
| 337 | */ | ||
| 338 | static final DateFormatSymbols getInstanceRef(Locale locale) { | ||
| 339 | DateFormatSymbols dfs = getProviderInstance(locale); | ||
| 340 | if (dfs != null) { | ||
| 341 | return dfs; | ||
| 342 | } | ||
| 343 | return getCachedInstance(locale); | ||
| 344 | } | ||
| 345 | |||
| 346 | private static DateFormatSymbols getProviderInstance(Locale locale) { | ||
| 347 | DateFormatSymbols providersInstance = null; | ||
| 348 | |||
| 349 | // Check whether a provider can provide an implementation that's closer | ||
| 350 | // to the requested locale than what the Java runtime itself can provide. | ||
| 351 | LocaleServiceProviderPool pool = | ||
| 352 | LocaleServiceProviderPool.getPool(DateFormatSymbolsProvider.class); | ||
| 353 | if (pool.hasProviders()) { | ||
| 354 | providersInstance = pool.getLocalizedObject( | ||
| 355 | DateFormatSymbolsGetter.INSTANCE, locale); | ||
| 356 | } | ||
| 357 | return providersInstance; | ||
| 358 | } | ||
| 359 | |||
| 360 | /** | ||
| 361 | * Returns a cached DateFormatSymbols if it's found in the | ||
| 362 | * cache. Otherwise, this method returns a newly cached instance | ||
| 363 | * for the given locale. | ||
| 364 | */ | ||
| 365 | private static DateFormatSymbols getCachedInstance(Locale locale) { | ||
| 366 | SoftReference<DateFormatSymbols> ref = cachedInstances.get(locale); | ||
| 367 | DateFormatSymbols dfs = null; | ||
| 368 | if (ref == null || (dfs = ref.get()) == null) { | ||
| 369 | dfs = new DateFormatSymbols(locale); | ||
| 370 | ref = new SoftReference<DateFormatSymbols>(dfs); | ||
| 371 | SoftReference<DateFormatSymbols> x = cachedInstances.putIfAbsent(locale, ref); | ||
| 372 | if (x != null) { | ||
| 373 | DateFormatSymbols y = x.get(); | ||
| 374 | if (y != null) { | ||
| 375 | dfs = y; | ||
| 376 | } else { | ||
| 377 | // Replace the empty SoftReference with ref. | ||
| 378 | cachedInstances.put(locale, ref); | ||
| 379 | } | ||
| 380 | } | ||
| 381 | } | ||
| 382 | return dfs; | ||
| 383 | } | ||
| 384 | |||
| 385 | /** | ||
| 386 | * Gets era strings. For example: "AD" and "BC". | ||
| 387 | * @return the era strings. | ||
| 388 | */ | ||
| 389 | public String[] getEras() { | ||
| 390 | return Arrays.copyOf(eras, eras.length); | ||
| 391 | } | ||
| 392 | |||
| 393 | /** | ||
| 394 | * Sets era strings. For example: "AD" and "BC". | ||
| 395 | * @param newEras the new era strings. | ||
| 396 | */ | ||
| 397 | public void setEras(String[] newEras) { | ||
| 398 | eras = Arrays.copyOf(newEras, newEras.length); | ||
| 399 | } | ||
| 400 | |||
| 401 | /** | ||
| 402 | * Gets month strings. For example: "January", "February", etc. | ||
| 403 | * @return the month strings. | ||
| 404 | */ | ||
| 405 | public String[] getMonths() { | ||
| 406 | return Arrays.copyOf(months, months.length); | ||
| 407 | } | ||
| 408 | |||
| 409 | /** | ||
| 410 | * Sets month strings. For example: "January", "February", etc. | ||
| 411 | * @param newMonths the new month strings. | ||
| 412 | */ | ||
| 413 | public void setMonths(String[] newMonths) { | ||
| 414 | months = Arrays.copyOf(newMonths, newMonths.length); | ||
| 415 | } | ||
| 416 | |||
| 417 | /** | ||
| 418 | * Gets short month strings. For example: "Jan", "Feb", etc. | ||
| 419 | * @return the short month strings. | ||
| 420 | */ | ||
| 421 | public String[] getShortMonths() { | ||
| 422 | return Arrays.copyOf(shortMonths, shortMonths.length); | ||
| 423 | } | ||
| 424 | |||
| 425 | /** | ||
| 426 | * Sets short month strings. For example: "Jan", "Feb", etc. | ||
| 427 | * @param newShortMonths the new short month strings. | ||
| 428 | */ | ||
| 429 | public void setShortMonths(String[] newShortMonths) { | ||
| 430 | shortMonths = Arrays.copyOf(newShortMonths, newShortMonths.length); | ||
| 431 | } | ||
| 432 | |||
| 433 | /** | ||
| 434 | * Gets weekday strings. For example: "Sunday", "Monday", etc. | ||
| 435 | * @return the weekday strings. Use <code>Calendar.SUNDAY</code>, | ||
| 436 | * <code>Calendar.MONDAY</code>, etc. to index the result array. | ||
| 437 | */ | ||
| 438 | public String[] getWeekdays() { | ||
| 439 | return Arrays.copyOf(weekdays, weekdays.length); | ||
| 440 | } | ||
| 441 | |||
| 442 | /** | ||
| 443 | * Sets weekday strings. For example: "Sunday", "Monday", etc. | ||
| 444 | * @param newWeekdays the new weekday strings. The array should | ||
| 445 | * be indexed by <code>Calendar.SUNDAY</code>, | ||
| 446 | * <code>Calendar.MONDAY</code>, etc. | ||
| 447 | */ | ||
| 448 | public void setWeekdays(String[] newWeekdays) { | ||
| 449 | weekdays = Arrays.copyOf(newWeekdays, newWeekdays.length); | ||
| 450 | } | ||
| 451 | |||
| 452 | /** | ||
| 453 | * Gets short weekday strings. For example: "Sun", "Mon", etc. | ||
| 454 | * @return the short weekday strings. Use <code>Calendar.SUNDAY</code>, | ||
| 455 | * <code>Calendar.MONDAY</code>, etc. to index the result array. | ||
| 456 | */ | ||
| 457 | public String[] getShortWeekdays() { | ||
| 458 | return Arrays.copyOf(shortWeekdays, shortWeekdays.length); | ||
| 459 | } | ||
| 460 | |||
| 461 | /** | ||
| 462 | * Sets short weekday strings. For example: "Sun", "Mon", etc. | ||
| 463 | * @param newShortWeekdays the new short weekday strings. The array should | ||
| 464 | * be indexed by <code>Calendar.SUNDAY</code>, | ||
| 465 | * <code>Calendar.MONDAY</code>, etc. | ||
| 466 | */ | ||
| 467 | public void setShortWeekdays(String[] newShortWeekdays) { | ||
| 468 | shortWeekdays = Arrays.copyOf(newShortWeekdays, newShortWeekdays.length); | ||
| 469 | } | ||
| 470 | |||
| 471 | /** | ||
| 472 | * Gets ampm strings. For example: "AM" and "PM". | ||
| 473 | * @return the ampm strings. | ||
| 474 | */ | ||
| 475 | public String[] getAmPmStrings() { | ||
| 476 | return Arrays.copyOf(ampms, ampms.length); | ||
| 477 | } | ||
| 478 | |||
| 479 | /** | ||
| 480 | * Sets ampm strings. For example: "AM" and "PM". | ||
| 481 | * @param newAmpms the new ampm strings. | ||
| 482 | */ | ||
| 483 | public void setAmPmStrings(String[] newAmpms) { | ||
| 484 | ampms = Arrays.copyOf(newAmpms, newAmpms.length); | ||
| 485 | } | ||
| 486 | |||
| 487 | /** | ||
| 488 | * Gets time zone strings. Use of this method is discouraged; use | ||
| 489 | * {@link java.util.TimeZone#getDisplayName() TimeZone.getDisplayName()} | ||
| 490 | * instead. | ||
| 491 | * <p> | ||
| 492 | * The value returned is a | ||
| 493 | * two-dimensional array of strings of size <em>n</em> by <em>m</em>, | ||
| 494 | * where <em>m</em> is at least 5. Each of the <em>n</em> rows is an | ||
| 495 | * entry containing the localized names for a single <code>TimeZone</code>. | ||
| 496 | * Each such row contains (with <code>i</code> ranging from | ||
| 497 | * 0..<em>n</em>-1): | ||
| 498 | * <ul> | ||
| 499 | * <li><code>zoneStrings[i][0]</code> - time zone ID</li> | ||
| 500 | * <li><code>zoneStrings[i][1]</code> - long name of zone in standard | ||
| 501 | * time</li> | ||
| 502 | * <li><code>zoneStrings[i][2]</code> - short name of zone in | ||
| 503 | * standard time</li> | ||
| 504 | * <li><code>zoneStrings[i][3]</code> - long name of zone in daylight | ||
| 505 | * saving time</li> | ||
| 506 | * <li><code>zoneStrings[i][4]</code> - short name of zone in daylight | ||
| 507 | * saving time</li> | ||
| 508 | * </ul> | ||
| 509 | * The zone ID is <em>not</em> localized; it's one of the valid IDs of | ||
| 510 | * the {@link java.util.TimeZone TimeZone} class that are not | ||
| 511 | * <a href="../util/TimeZone.html#CustomID">custom IDs</a>. | ||
| 512 | * All other entries are localized names. If a zone does not implement | ||
| 513 | * daylight saving time, the daylight saving time names should not be used. | ||
| 514 | * <p> | ||
| 515 | * If {@link #setZoneStrings(String[][]) setZoneStrings} has been called | ||
| 516 | * on this <code>DateFormatSymbols</code> instance, then the strings | ||
| 517 | * provided by that call are returned. Otherwise, the returned array | ||
| 518 | * contains names provided by the Java runtime and by installed | ||
| 519 | * {@link java.util.spi.TimeZoneNameProvider TimeZoneNameProvider} | ||
| 520 | * implementations. | ||
| 521 | * | ||
| 522 | * @return the time zone strings. | ||
| 523 | * @see #setZoneStrings(String[][]) | ||
| 524 | */ | ||
| 525 | public String[][] getZoneStrings() { | ||
| 526 | return getZoneStringsImpl(true); | ||
| 527 | } | ||
| 528 | |||
| 529 | /** | ||
| 530 | * Sets time zone strings. The argument must be a | ||
| 531 | * two-dimensional array of strings of size <em>n</em> by <em>m</em>, | ||
| 532 | * where <em>m</em> is at least 5. Each of the <em>n</em> rows is an | ||
| 533 | * entry containing the localized names for a single <code>TimeZone</code>. | ||
| 534 | * Each such row contains (with <code>i</code> ranging from | ||
| 535 | * 0..<em>n</em>-1): | ||
| 536 | * <ul> | ||
| 537 | * <li><code>zoneStrings[i][0]</code> - time zone ID</li> | ||
| 538 | * <li><code>zoneStrings[i][1]</code> - long name of zone in standard | ||
| 539 | * time</li> | ||
| 540 | * <li><code>zoneStrings[i][2]</code> - short name of zone in | ||
| 541 | * standard time</li> | ||
| 542 | * <li><code>zoneStrings[i][3]</code> - long name of zone in daylight | ||
| 543 | * saving time</li> | ||
| 544 | * <li><code>zoneStrings[i][4]</code> - short name of zone in daylight | ||
| 545 | * saving time</li> | ||
| 546 | * </ul> | ||
| 547 | * The zone ID is <em>not</em> localized; it's one of the valid IDs of | ||
| 548 | * the {@link java.util.TimeZone TimeZone} class that are not | ||
| 549 | * <a href="../util/TimeZone.html#CustomID">custom IDs</a>. | ||
| 550 | * All other entries are localized names. | ||
| 551 | * | ||
| 552 | * @param newZoneStrings the new time zone strings. | ||
| 553 | * @exception IllegalArgumentException if the length of any row in | ||
| 554 | * <code>newZoneStrings</code> is less than 5 | ||
| 555 | * @exception NullPointerException if <code>newZoneStrings</code> is null | ||
| 556 | * @see #getZoneStrings() | ||
| 557 | */ | ||
| 558 | public void setZoneStrings(String[][] newZoneStrings) { | ||
| 559 | String[][] aCopy = new String[newZoneStrings.length][]; | ||
| 560 | for (int i = 0; i < newZoneStrings.length; ++i) { | ||
| 561 | int len = newZoneStrings[i].length; | ||
| 562 | if (len < 5) { | ||
| 563 | throw new IllegalArgumentException(); | ||
| 564 | } | ||
| 565 | aCopy[i] = Arrays.copyOf(newZoneStrings[i], len); | ||
| 566 | } | ||
| 567 | zoneStrings = aCopy; | ||
| 568 | isZoneStringsSet = true; | ||
| 569 | } | ||
| 570 | |||
| 571 | /** | ||
| 572 | * Gets localized date-time pattern characters. For example: 'u', 't', etc. | ||
| 573 | * @return the localized date-time pattern characters. | ||
| 574 | */ | ||
| 575 | public String getLocalPatternChars() { | ||
| 576 | return localPatternChars; | ||
| 577 | } | ||
| 578 | |||
| 579 | /** | ||
| 580 | * Sets localized date-time pattern characters. For example: 'u', 't', etc. | ||
| 581 | * @param newLocalPatternChars the new localized date-time | ||
| 582 | * pattern characters. | ||
| 583 | */ | ||
| 584 | public void setLocalPatternChars(String newLocalPatternChars) { | ||
| 585 | // Call toString() to throw an NPE in case the argument is null | ||
| 586 | localPatternChars = newLocalPatternChars.toString(); | ||
| 587 | } | ||
| 588 | |||
| 589 | /** | ||
| 590 | * Overrides Cloneable | ||
| 591 | */ | ||
| 592 | public Object clone() | ||
| 593 | { | ||
| 594 | try | ||
| 595 | { | ||
| 596 | DateFormatSymbols other = (DateFormatSymbols)super.clone(); | ||
| 597 | copyMembers(this, other); | ||
| 598 | return other; | ||
| 599 | } catch (CloneNotSupportedException e) { | ||
| 600 | throw new InternalError(); | ||
| 601 | } | ||
| 602 | } | ||
| 603 | |||
| 604 | /** | ||
| 605 | * Override hashCode. | ||
| 606 | * Generates a hash code for the DateFormatSymbols object. | ||
| 607 | */ | ||
| 608 | public int hashCode() { | ||
| 609 | int hashcode = 0; | ||
| 610 | String[][] zoneStrings = getZoneStringsWrapper(); | ||
| 611 | for (int index = 0; index < zoneStrings[0].length; ++index) | ||
| 612 | hashcode ^= zoneStrings[0][index].hashCode(); | ||
| 613 | return hashcode; | ||
| 614 | } | ||
| 615 | |||
| 616 | /** | ||
| 617 | * Override equals | ||
| 618 | */ | ||
| 619 | public boolean equals(Object obj) | ||
| 620 | { | ||
| 621 | if (this == obj) return true; | ||
| 622 | if (obj == null || getClass() != obj.getClass()) return false; | ||
| 623 | DateFormatSymbols that = (DateFormatSymbols) obj; | ||
| 624 | return (Arrays.equals(eras, that.eras) | ||
| 625 | && Arrays.equals(months, that.months) | ||
| 626 | && Arrays.equals(shortMonths, that.shortMonths) | ||
| 627 | && Arrays.equals(weekdays, that.weekdays) | ||
| 628 | && Arrays.equals(shortWeekdays, that.shortWeekdays) | ||
| 629 | && Arrays.equals(ampms, that.ampms) | ||
| 630 | && Arrays.deepEquals(getZoneStringsWrapper(), that.getZoneStringsWrapper()) | ||
| 631 | && ((localPatternChars != null | ||
| 632 | && localPatternChars.equals(that.localPatternChars)) | ||
| 633 | || (localPatternChars == null | ||
| 634 | && that.localPatternChars == null))); | ||
| 635 | } | ||
| 636 | |||
| 637 | // =======================privates=============================== | ||
| 638 | |||
| 639 | /** | ||
| 640 | * Useful constant for defining time zone offsets. | ||
| 641 | */ | ||
| 642 | static final int millisPerHour = 60*60*1000; | ||
| 643 | |||
| 644 | /** | ||
| 645 | * Cache to hold DateFormatSymbols instances per Locale. | ||
| 646 | */ | ||
| 647 | private static final ConcurrentMap<Locale, SoftReference<DateFormatSymbols>> cachedInstances | ||
| 648 | = new ConcurrentHashMap<Locale, SoftReference<DateFormatSymbols>>(3); | ||
| 649 | |||
| 650 | private void initializeData(Locale desiredLocale) { | ||
| 651 | locale = desiredLocale; | ||
| 652 | |||
| 653 | // Copy values of a cached instance if any. | ||
| 654 | SoftReference<DateFormatSymbols> ref = cachedInstances.get(locale); | ||
| 655 | DateFormatSymbols dfs; | ||
| 656 | if (ref != null && (dfs = ref.get()) != null) { | ||
| 657 | copyMembers(dfs, this); | ||
| 658 | return; | ||
| 659 | } | ||
| 660 | |||
| 661 | // Initialize the fields from the ResourceBundle for locale. | ||
| 662 | ResourceBundle resource = LocaleData.getDateFormatData(locale); | ||
| 663 | |||
| 664 | eras = resource.getStringArray("Eras"); | ||
| 665 | months = resource.getStringArray("MonthNames"); | ||
| 666 | shortMonths = resource.getStringArray("MonthAbbreviations"); | ||
| 667 | ampms = resource.getStringArray("AmPmMarkers"); | ||
| 668 | localPatternChars = resource.getString("DateTimePatternChars"); | ||
| 669 | |||
| 670 | // Day of week names are stored in a 1-based array. | ||
| 671 | weekdays = toOneBasedArray(resource.getStringArray("DayNames")); | ||
| 672 | shortWeekdays = toOneBasedArray(resource.getStringArray("DayAbbreviations")); | ||
| 673 | } | ||
| 674 | |||
| 675 | private static String[] toOneBasedArray(String[] src) { | ||
| 676 | int len = src.length; | ||
| 677 | String[] dst = new String[len + 1]; | ||
| 678 | dst[0] = ""; | ||
| 679 | for (int i = 0; i < len; i++) { | ||
| 680 | dst[i + 1] = src[i]; | ||
| 681 | } | ||
| 682 | return dst; | ||
| 683 | } | ||
| 684 | |||
| 685 | /** | ||
| 686 | * Package private: used by SimpleDateFormat | ||
| 687 | * Gets the index for the given time zone ID to obtain the time zone | ||
| 688 | * strings for formatting. The time zone ID is just for programmatic | ||
| 689 | * lookup. NOT LOCALIZED!!! | ||
| 690 | * @param ID the given time zone ID. | ||
| 691 | * @return the index of the given time zone ID. Returns -1 if | ||
| 692 | * the given time zone ID can't be located in the DateFormatSymbols object. | ||
| 693 | * @see java.util.SimpleTimeZone | ||
| 694 | */ | ||
| 695 | final int getZoneIndex(String ID) | ||
| 696 | { | ||
| 697 | String[][] zoneStrings = getZoneStringsWrapper(); | ||
| 698 | for (int index=0; index<zoneStrings.length; index++) | ||
| 699 | { | ||
| 700 | if (ID.equals(zoneStrings[index][0])) return index; | ||
| 701 | } | ||
| 702 | |||
| 703 | return -1; | ||
| 704 | } | ||
| 705 | |||
| 706 | /** | ||
| 707 | * Wrapper method to the getZoneStrings(), which is called from inside | ||
| 708 | * the java.text package and not to mutate the returned arrays, so that | ||
| 709 | * it does not need to create a defensive copy. | ||
| 710 | */ | ||
| 711 | final String[][] getZoneStringsWrapper() { | ||
| 712 | if (isSubclassObject()) { | ||
| 713 | return getZoneStrings(); | ||
| 714 | } else { | ||
| 715 | return getZoneStringsImpl(false); | ||
| 716 | } | ||
| 717 | } | ||
| 718 | |||
| 719 | private final String[][] getZoneStringsImpl(boolean needsCopy) { | ||
| 720 | if (zoneStrings == null) { | ||
| 721 | zoneStrings = TimeZoneNameUtility.getZoneStrings(locale); | ||
| 722 | } | ||
| 723 | |||
| 724 | if (!needsCopy) { | ||
| 725 | return zoneStrings; | ||
| 726 | } | ||
| 727 | |||
| 728 | int len = zoneStrings.length; | ||
| 729 | String[][] aCopy = new String[len][]; | ||
| 730 | for (int i = 0; i < len; i++) { | ||
| 731 | aCopy[i] = Arrays.copyOf(zoneStrings[i], zoneStrings[i].length); | ||
| 732 | } | ||
| 733 | return aCopy; | ||
| 734 | } | ||
| 735 | |||
| 736 | private final boolean isSubclassObject() { | ||
| 737 | return !getClass().getName().equals("java.text.DateFormatSymbols"); | ||
| 738 | } | ||
| 739 | |||
| 740 | /** | ||
| 741 | * Clones all the data members from the source DateFormatSymbols to | ||
| 742 | * the target DateFormatSymbols. This is only for subclasses. | ||
| 743 | * @param src the source DateFormatSymbols. | ||
| 744 | * @param dst the target DateFormatSymbols. | ||
| 745 | */ | ||
| 746 | private final void copyMembers(DateFormatSymbols src, DateFormatSymbols dst) | ||
| 747 | { | ||
| 748 | dst.eras = Arrays.copyOf(src.eras, src.eras.length); | ||
| 749 | dst.months = Arrays.copyOf(src.months, src.months.length); | ||
| 750 | dst.shortMonths = Arrays.copyOf(src.shortMonths, src.shortMonths.length); | ||
| 751 | dst.weekdays = Arrays.copyOf(src.weekdays, src.weekdays.length); | ||
| 752 | dst.shortWeekdays = Arrays.copyOf(src.shortWeekdays, src.shortWeekdays.length); | ||
| 753 | dst.ampms = Arrays.copyOf(src.ampms, src.ampms.length); | ||
| 754 | if (src.zoneStrings != null) { | ||
| 755 | dst.zoneStrings = src.getZoneStringsImpl(true); | ||
| 756 | } else { | ||
| 757 | dst.zoneStrings = null; | ||
| 758 | } | ||
| 759 | dst.localPatternChars = src.localPatternChars; | ||
| 760 | } | ||
| 761 | |||
| 762 | /** | ||
| 763 | * Write out the default serializable data, after ensuring the | ||
| 764 | * <code>zoneStrings</code> field is initialized in order to make | ||
| 765 | * sure the backward compatibility. | ||
| 766 | * | ||
| 767 | * @since 1.6 | ||
| 768 | */ | ||
| 769 | private void writeObject(ObjectOutputStream stream) throws IOException { | ||
| 770 | if (zoneStrings == null) { | ||
| 771 | zoneStrings = TimeZoneNameUtility.getZoneStrings(locale); | ||
| 772 | } | ||
| 773 | stream.defaultWriteObject(); | ||
| 774 | } | ||
| 775 | |||
| 776 | /** | ||
| 777 | * Obtains a DateFormatSymbols instance from a DateFormatSymbolsProvider | ||
| 778 | * implementation. | ||
| 779 | */ | ||
| 780 | private static class DateFormatSymbolsGetter | ||
| 781 | implements LocaleServiceProviderPool.LocalizedObjectGetter<DateFormatSymbolsProvider, | ||
| 782 | DateFormatSymbols> { | ||
| 783 | private static final DateFormatSymbolsGetter INSTANCE = | ||
| 784 | new DateFormatSymbolsGetter(); | ||
| 785 | |||
| 786 | public DateFormatSymbols getObject(DateFormatSymbolsProvider dateFormatSymbolsProvider, | ||
| 787 | Locale locale, | ||
| 788 | String key, | ||
| 789 | Object... params) { | ||
| 790 | assert params.length == 0; | ||
| 791 | return dateFormatSymbolsProvider.getInstance(locale); | ||
| 792 | } | ||
| 793 | } | ||
| 794 | } | ||
Binary file not shown.
| @@ -0,0 +1,3278 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.io.InvalidObjectException; | ||
| 42 | import java.io.IOException; | ||
| 43 | import java.io.ObjectInputStream; | ||
| 44 | import java.math.BigDecimal; | ||
| 45 | import java.math.BigInteger; | ||
| 46 | import java.math.RoundingMode; | ||
| 47 | import java.util.ArrayList; | ||
| 48 | import java.util.Currency; | ||
| 49 | import java.util.Locale; | ||
| 50 | import java.util.ResourceBundle; | ||
| 51 | import java.util.concurrent.ConcurrentHashMap; | ||
| 52 | import java.util.concurrent.ConcurrentMap; | ||
| 53 | import java.util.concurrent.atomic.AtomicInteger; | ||
| 54 | import java.util.concurrent.atomic.AtomicLong; | ||
| 55 | import sun.util.resources.LocaleData; | ||
| 56 | |||
| 57 | /** | ||
| 58 | * <code>DecimalFormat</code> is a concrete subclass of | ||
| 59 | * <code>NumberFormat</code> that formats decimal numbers. It has a variety of | ||
| 60 | * features designed to make it possible to parse and format numbers in any | ||
| 61 | * locale, including support for Western, Arabic, and Indic digits. It also | ||
| 62 | * supports different kinds of numbers, including integers (123), fixed-point | ||
| 63 | * numbers (123.4), scientific notation (1.23E4), percentages (12%), and | ||
| 64 | * currency amounts ($123). All of these can be localized. | ||
| 65 | * | ||
| 66 | * <p>To obtain a <code>NumberFormat</code> for a specific locale, including the | ||
| 67 | * default locale, call one of <code>NumberFormat</code>'s factory methods, such | ||
| 68 | * as <code>getInstance()</code>. In general, do not call the | ||
| 69 | * <code>DecimalFormat</code> constructors directly, since the | ||
| 70 | * <code>NumberFormat</code> factory methods may return subclasses other than | ||
| 71 | * <code>DecimalFormat</code>. If you need to customize the format object, do | ||
| 72 | * something like this: | ||
| 73 | * | ||
| 74 | * <blockquote><pre> | ||
| 75 | * NumberFormat f = NumberFormat.getInstance(loc); | ||
| 76 | * if (f instanceof DecimalFormat) { | ||
| 77 | * ((DecimalFormat) f).setDecimalSeparatorAlwaysShown(true); | ||
| 78 | * } | ||
| 79 | * </pre></blockquote> | ||
| 80 | * | ||
| 81 | * <p>A <code>DecimalFormat</code> comprises a <em>pattern</em> and a set of | ||
| 82 | * <em>symbols</em>. The pattern may be set directly using | ||
| 83 | * <code>applyPattern()</code>, or indirectly using the API methods. The | ||
| 84 | * symbols are stored in a <code>DecimalFormatSymbols</code> object. When using | ||
| 85 | * the <code>NumberFormat</code> factory methods, the pattern and symbols are | ||
| 86 | * read from localized <code>ResourceBundle</code>s. | ||
| 87 | * | ||
| 88 | * <h4>Patterns</h4> | ||
| 89 | * | ||
| 90 | * <code>DecimalFormat</code> patterns have the following syntax: | ||
| 91 | * <blockquote><pre> | ||
| 92 | * <i>Pattern:</i> | ||
| 93 | * <i>PositivePattern</i> | ||
| 94 | * <i>PositivePattern</i> ; <i>NegativePattern</i> | ||
| 95 | * <i>PositivePattern:</i> | ||
| 96 | * <i>Prefix<sub>opt</sub></i> <i>Number</i> <i>Suffix<sub>opt</sub></i> | ||
| 97 | * <i>NegativePattern:</i> | ||
| 98 | * <i>Prefix<sub>opt</sub></i> <i>Number</i> <i>Suffix<sub>opt</sub></i> | ||
| 99 | * <i>Prefix:</i> | ||
| 100 | * any Unicode characters except \uFFFE, \uFFFF, and special characters | ||
| 101 | * <i>Suffix:</i> | ||
| 102 | * any Unicode characters except \uFFFE, \uFFFF, and special characters | ||
| 103 | * <i>Number:</i> | ||
| 104 | * <i>Integer</i> <i>Exponent<sub>opt</sub></i> | ||
| 105 | * <i>Integer</i> . <i>Fraction</i> <i>Exponent<sub>opt</sub></i> | ||
| 106 | * <i>Integer:</i> | ||
| 107 | * <i>MinimumInteger</i> | ||
| 108 | * # | ||
| 109 | * # <i>Integer</i> | ||
| 110 | * # , <i>Integer</i> | ||
| 111 | * <i>MinimumInteger:</i> | ||
| 112 | * 0 | ||
| 113 | * 0 <i>MinimumInteger</i> | ||
| 114 | * 0 , <i>MinimumInteger</i> | ||
| 115 | * <i>Fraction:</i> | ||
| 116 | * <i>MinimumFraction<sub>opt</sub></i> <i>OptionalFraction<sub>opt</sub></i> | ||
| 117 | * <i>MinimumFraction:</i> | ||
| 118 | * 0 <i>MinimumFraction<sub>opt</sub></i> | ||
| 119 | * <i>OptionalFraction:</i> | ||
| 120 | * # <i>OptionalFraction<sub>opt</sub></i> | ||
| 121 | * <i>Exponent:</i> | ||
| 122 | * E <i>MinimumExponent</i> | ||
| 123 | * <i>MinimumExponent:</i> | ||
| 124 | * 0 <i>MinimumExponent<sub>opt</sub></i> | ||
| 125 | * </pre></blockquote> | ||
| 126 | * | ||
| 127 | * <p>A <code>DecimalFormat</code> pattern contains a positive and negative | ||
| 128 | * subpattern, for example, <code>"#,##0.00;(#,##0.00)"</code>. Each | ||
| 129 | * subpattern has a prefix, numeric part, and suffix. The negative subpattern | ||
| 130 | * is optional; if absent, then the positive subpattern prefixed with the | ||
| 131 | * localized minus sign (<code>'-'</code> in most locales) is used as the | ||
| 132 | * negative subpattern. That is, <code>"0.00"</code> alone is equivalent to | ||
| 133 | * <code>"0.00;-0.00"</code>. If there is an explicit negative subpattern, it | ||
| 134 | * serves only to specify the negative prefix and suffix; the number of digits, | ||
| 135 | * minimal digits, and other characteristics are all the same as the positive | ||
| 136 | * pattern. That means that <code>"#,##0.0#;(#)"</code> produces precisely | ||
| 137 | * the same behavior as <code>"#,##0.0#;(#,##0.0#)"</code>. | ||
| 138 | * | ||
| 139 | * <p>The prefixes, suffixes, and various symbols used for infinity, digits, | ||
| 140 | * thousands separators, decimal separators, etc. may be set to arbitrary | ||
| 141 | * values, and they will appear properly during formatting. However, care must | ||
| 142 | * be taken that the symbols and strings do not conflict, or parsing will be | ||
| 143 | * unreliable. For example, either the positive and negative prefixes or the | ||
| 144 | * suffixes must be distinct for <code>DecimalFormat.parse()</code> to be able | ||
| 145 | * to distinguish positive from negative values. (If they are identical, then | ||
| 146 | * <code>DecimalFormat</code> will behave as if no negative subpattern was | ||
| 147 | * specified.) Another example is that the decimal separator and thousands | ||
| 148 | * separator should be distinct characters, or parsing will be impossible. | ||
| 149 | * | ||
| 150 | * <p>The grouping separator is commonly used for thousands, but in some | ||
| 151 | * countries it separates ten-thousands. The grouping size is a constant number | ||
| 152 | * of digits between the grouping characters, such as 3 for 100,000,000 or 4 for | ||
| 153 | * 1,0000,0000. If you supply a pattern with multiple grouping characters, the | ||
| 154 | * interval between the last one and the end of the integer is the one that is | ||
| 155 | * used. So <code>"#,##,###,####"</code> == <code>"######,####"</code> == | ||
| 156 | * <code>"##,####,####"</code>. | ||
| 157 | * | ||
| 158 | * <h4>Special Pattern Characters</h4> | ||
| 159 | * | ||
| 160 | * <p>Many characters in a pattern are taken literally; they are matched during | ||
| 161 | * parsing and output unchanged during formatting. Special characters, on the | ||
| 162 | * other hand, stand for other characters, strings, or classes of characters. | ||
| 163 | * They must be quoted, unless noted otherwise, if they are to appear in the | ||
| 164 | * prefix or suffix as literals. | ||
| 165 | * | ||
| 166 | * <p>The characters listed here are used in non-localized patterns. Localized | ||
| 167 | * patterns use the corresponding characters taken from this formatter's | ||
| 168 | * <code>DecimalFormatSymbols</code> object instead, and these characters lose | ||
| 169 | * their special status. Two exceptions are the currency sign and quote, which | ||
| 170 | * are not localized. | ||
| 171 | * | ||
| 172 | * <blockquote> | ||
| 173 | * <table border=0 cellspacing=3 cellpadding=0 summary="Chart showing symbol, | ||
| 174 | * location, localized, and meaning."> | ||
| 175 | * <tr bgcolor="#ccccff"> | ||
| 176 | * <th align=left>Symbol | ||
| 177 | * <th align=left>Location | ||
| 178 | * <th align=left>Localized? | ||
| 179 | * <th align=left>Meaning | ||
| 180 | * <tr valign=top> | ||
| 181 | * <td><code>0</code> | ||
| 182 | * <td>Number | ||
| 183 | * <td>Yes | ||
| 184 | * <td>Digit | ||
| 185 | * <tr valign=top bgcolor="#eeeeff"> | ||
| 186 | * <td><code>#</code> | ||
| 187 | * <td>Number | ||
| 188 | * <td>Yes | ||
| 189 | * <td>Digit, zero shows as absent | ||
| 190 | * <tr valign=top> | ||
| 191 | * <td><code>.</code> | ||
| 192 | * <td>Number | ||
| 193 | * <td>Yes | ||
| 194 | * <td>Decimal separator or monetary decimal separator | ||
| 195 | * <tr valign=top bgcolor="#eeeeff"> | ||
| 196 | * <td><code>-</code> | ||
| 197 | * <td>Number | ||
| 198 | * <td>Yes | ||
| 199 | * <td>Minus sign | ||
| 200 | * <tr valign=top> | ||
| 201 | * <td><code>,</code> | ||
| 202 | * <td>Number | ||
| 203 | * <td>Yes | ||
| 204 | * <td>Grouping separator | ||
| 205 | * <tr valign=top bgcolor="#eeeeff"> | ||
| 206 | * <td><code>E</code> | ||
| 207 | * <td>Number | ||
| 208 | * <td>Yes | ||
| 209 | * <td>Separates mantissa and exponent in scientific notation. | ||
| 210 | * <em>Need not be quoted in prefix or suffix.</em> | ||
| 211 | * <tr valign=top> | ||
| 212 | * <td><code>;</code> | ||
| 213 | * <td>Subpattern boundary | ||
| 214 | * <td>Yes | ||
| 215 | * <td>Separates positive and negative subpatterns | ||
| 216 | * <tr valign=top bgcolor="#eeeeff"> | ||
| 217 | * <td><code>%</code> | ||
| 218 | * <td>Prefix or suffix | ||
| 219 | * <td>Yes | ||
| 220 | * <td>Multiply by 100 and show as percentage | ||
| 221 | * <tr valign=top> | ||
| 222 | * <td><code>\u2030</code> | ||
| 223 | * <td>Prefix or suffix | ||
| 224 | * <td>Yes | ||
| 225 | * <td>Multiply by 1000 and show as per mille value | ||
| 226 | * <tr valign=top bgcolor="#eeeeff"> | ||
| 227 | * <td><code>¤</code> (<code>\u00A4</code>) | ||
| 228 | * <td>Prefix or suffix | ||
| 229 | * <td>No | ||
| 230 | * <td>Currency sign, replaced by currency symbol. If | ||
| 231 | * doubled, replaced by international currency symbol. | ||
| 232 | * If present in a pattern, the monetary decimal separator | ||
| 233 | * is used instead of the decimal separator. | ||
| 234 | * <tr valign=top> | ||
| 235 | * <td><code>'</code> | ||
| 236 | * <td>Prefix or suffix | ||
| 237 | * <td>No | ||
| 238 | * <td>Used to quote special characters in a prefix or suffix, | ||
| 239 | * for example, <code>"'#'#"</code> formats 123 to | ||
| 240 | * <code>"#123"</code>. To create a single quote | ||
| 241 | * itself, use two in a row: <code>"# o''clock"</code>. | ||
| 242 | * </table> | ||
| 243 | * </blockquote> | ||
| 244 | * | ||
| 245 | * <h4>Scientific Notation</h4> | ||
| 246 | * | ||
| 247 | * <p>Numbers in scientific notation are expressed as the product of a mantissa | ||
| 248 | * and a power of ten, for example, 1234 can be expressed as 1.234 x 10^3. The | ||
| 249 | * mantissa is often in the range 1.0 <= x < 10.0, but it need not be. | ||
| 250 | * <code>DecimalFormat</code> can be instructed to format and parse scientific | ||
| 251 | * notation <em>only via a pattern</em>; there is currently no factory method | ||
| 252 | * that creates a scientific notation format. In a pattern, the exponent | ||
| 253 | * character immediately followed by one or more digit characters indicates | ||
| 254 | * scientific notation. Example: <code>"0.###E0"</code> formats the number | ||
| 255 | * 1234 as <code>"1.234E3"</code>. | ||
| 256 | * | ||
| 257 | * <ul> | ||
| 258 | * <li>The number of digit characters after the exponent character gives the | ||
| 259 | * minimum exponent digit count. There is no maximum. Negative exponents are | ||
| 260 | * formatted using the localized minus sign, <em>not</em> the prefix and suffix | ||
| 261 | * from the pattern. This allows patterns such as <code>"0.###E0 m/s"</code>. | ||
| 262 | * | ||
| 263 | * <li>The minimum and maximum number of integer digits are interpreted | ||
| 264 | * together: | ||
| 265 | * | ||
| 266 | * <ul> | ||
| 267 | * <li>If the maximum number of integer digits is greater than their minimum number | ||
| 268 | * and greater than 1, it forces the exponent to be a multiple of the maximum | ||
| 269 | * number of integer digits, and the minimum number of integer digits to be | ||
| 270 | * interpreted as 1. The most common use of this is to generate | ||
| 271 | * <em>engineering notation</em>, in which the exponent is a multiple of three, | ||
| 272 | * e.g., <code>"##0.#####E0"</code>. Using this pattern, the number 12345 | ||
| 273 | * formats to <code>"12.345E3"</code>, and 123456 formats to | ||
| 274 | * <code>"123.456E3"</code>. | ||
| 275 | * | ||
| 276 | * <li>Otherwise, the minimum number of integer digits is achieved by adjusting the | ||
| 277 | * exponent. Example: 0.00123 formatted with <code>"00.###E0"</code> yields | ||
| 278 | * <code>"12.3E-4"</code>. | ||
| 279 | * </ul> | ||
| 280 | * | ||
| 281 | * <li>The number of significant digits in the mantissa is the sum of the | ||
| 282 | * <em>minimum integer</em> and <em>maximum fraction</em> digits, and is | ||
| 283 | * unaffected by the maximum integer digits. For example, 12345 formatted with | ||
| 284 | * <code>"##0.##E0"</code> is <code>"12.3E3"</code>. To show all digits, set | ||
| 285 | * the significant digits count to zero. The number of significant digits | ||
| 286 | * does not affect parsing. | ||
| 287 | * | ||
| 288 | * <li>Exponential patterns may not contain grouping separators. | ||
| 289 | * </ul> | ||
| 290 | * | ||
| 291 | * <h4>Rounding</h4> | ||
| 292 | * | ||
| 293 | * <code>DecimalFormat</code> provides rounding modes defined in | ||
| 294 | * {@link java.math.RoundingMode} for formatting. By default, it uses | ||
| 295 | * {@link java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}. | ||
| 296 | * | ||
| 297 | * <h4>Digits</h4> | ||
| 298 | * | ||
| 299 | * For formatting, <code>DecimalFormat</code> uses the ten consecutive | ||
| 300 | * characters starting with the localized zero digit defined in the | ||
| 301 | * <code>DecimalFormatSymbols</code> object as digits. For parsing, these | ||
| 302 | * digits as well as all Unicode decimal digits, as defined by | ||
| 303 | * {@link Character#digit Character.digit}, are recognized. | ||
| 304 | * | ||
| 305 | * <h4>Special Values</h4> | ||
| 306 | * | ||
| 307 | * <p><code>NaN</code> is formatted as a string, which typically has a single character | ||
| 308 | * <code>\uFFFD</code>. This string is determined by the | ||
| 309 | * <code>DecimalFormatSymbols</code> object. This is the only value for which | ||
| 310 | * the prefixes and suffixes are not used. | ||
| 311 | * | ||
| 312 | * <p>Infinity is formatted as a string, which typically has a single character | ||
| 313 | * <code>\u221E</code>, with the positive or negative prefixes and suffixes | ||
| 314 | * applied. The infinity string is determined by the | ||
| 315 | * <code>DecimalFormatSymbols</code> object. | ||
| 316 | * | ||
| 317 | * <p>Negative zero (<code>"-0"</code>) parses to | ||
| 318 | * <ul> | ||
| 319 | * <li><code>BigDecimal(0)</code> if <code>isParseBigDecimal()</code> is | ||
| 320 | * true, | ||
| 321 | * <li><code>Long(0)</code> if <code>isParseBigDecimal()</code> is false | ||
| 322 | * and <code>isParseIntegerOnly()</code> is true, | ||
| 323 | * <li><code>Double(-0.0)</code> if both <code>isParseBigDecimal()</code> | ||
| 324 | * and <code>isParseIntegerOnly()</code> are false. | ||
| 325 | * </ul> | ||
| 326 | * | ||
| 327 | * <h4><a name="synchronization">Synchronization</a></h4> | ||
| 328 | * | ||
| 329 | * <p> | ||
| 330 | * Decimal formats are generally not synchronized. | ||
| 331 | * It is recommended to create separate format instances for each thread. | ||
| 332 | * If multiple threads access a format concurrently, it must be synchronized | ||
| 333 | * externally. | ||
| 334 | * | ||
| 335 | * <h4>Example</h4> | ||
| 336 | * | ||
| 337 | * <blockquote><pre> | ||
| 338 | * <strong>// Print out a number using the localized number, integer, currency, | ||
| 339 | * // and percent format for each locale</strong> | ||
| 340 | * Locale[] locales = NumberFormat.getAvailableLocales(); | ||
| 341 | * double myNumber = -1234.56; | ||
| 342 | * NumberFormat form; | ||
| 343 | * for (int j=0; j<4; ++j) { | ||
| 344 | * System.out.println("FORMAT"); | ||
| 345 | * for (int i = 0; i < locales.length; ++i) { | ||
| 346 | * if (locales[i].getCountry().length() == 0) { | ||
| 347 | * continue; // Skip language-only locales | ||
| 348 | * } | ||
| 349 | * System.out.print(locales[i].getDisplayName()); | ||
| 350 | * switch (j) { | ||
| 351 | * case 0: | ||
| 352 | * form = NumberFormat.getInstance(locales[i]); break; | ||
| 353 | * case 1: | ||
| 354 | * form = NumberFormat.getIntegerInstance(locales[i]); break; | ||
| 355 | * case 2: | ||
| 356 | * form = NumberFormat.getCurrencyInstance(locales[i]); break; | ||
| 357 | * default: | ||
| 358 | * form = NumberFormat.getPercentInstance(locales[i]); break; | ||
| 359 | * } | ||
| 360 | * if (form instanceof DecimalFormat) { | ||
| 361 | * System.out.print(": " + ((DecimalFormat) form).toPattern()); | ||
| 362 | * } | ||
| 363 | * System.out.print(" -> " + form.format(myNumber)); | ||
| 364 | * try { | ||
| 365 | * System.out.println(" -> " + form.parse(form.format(myNumber))); | ||
| 366 | * } catch (ParseException e) {} | ||
| 367 | * } | ||
| 368 | * } | ||
| 369 | * </pre></blockquote> | ||
| 370 | * | ||
| 371 | * @see <a href="http://java.sun.com/docs/books/tutorial/i18n/format/decimalFormat.html">Java Tutorial</a> | ||
| 372 | * @see NumberFormat | ||
| 373 | * @see DecimalFormatSymbols | ||
| 374 | * @see ParsePosition | ||
| 375 | * @author Mark Davis | ||
| 376 | * @author Alan Liu | ||
| 377 | */ | ||
| 378 | public class DecimalFormat extends NumberFormat { | ||
| 379 | |||
| 380 | /** | ||
| 381 | * Creates a DecimalFormat using the default pattern and symbols | ||
| 382 | * for the default locale. This is a convenient way to obtain a | ||
| 383 | * DecimalFormat when internationalization is not the main concern. | ||
| 384 | * <p> | ||
| 385 | * To obtain standard formats for a given locale, use the factory methods | ||
| 386 | * on NumberFormat such as getNumberInstance. These factories will | ||
| 387 | * return the most appropriate sub-class of NumberFormat for a given | ||
| 388 | * locale. | ||
| 389 | * | ||
| 390 | * @see java.text.NumberFormat#getInstance | ||
| 391 | * @see java.text.NumberFormat#getNumberInstance | ||
| 392 | * @see java.text.NumberFormat#getCurrencyInstance | ||
| 393 | * @see java.text.NumberFormat#getPercentInstance | ||
| 394 | */ | ||
| 395 | public DecimalFormat() { | ||
| 396 | Locale def = Locale.getDefault(Locale.Category.FORMAT); | ||
| 397 | // try to get the pattern from the cache | ||
| 398 | String pattern = cachedLocaleData.get(def); | ||
| 399 | if (pattern == null) { /* cache miss */ | ||
| 400 | // Get the pattern for the default locale. | ||
| 401 | ResourceBundle rb = LocaleData.getNumberFormatData(def); | ||
| 402 | String[] all = rb.getStringArray("NumberPatterns"); | ||
| 403 | pattern = all[0]; | ||
| 404 | /* update cache */ | ||
| 405 | cachedLocaleData.putIfAbsent(def, pattern); | ||
| 406 | } | ||
| 407 | |||
| 408 | // Always applyPattern after the symbols are set | ||
| 409 | this.symbols = new DecimalFormatSymbols(def); | ||
| 410 | applyPattern(pattern, false); | ||
| 411 | } | ||
| 412 | |||
| 413 | |||
| 414 | /** | ||
| 415 | * Creates a DecimalFormat using the given pattern and the symbols | ||
| 416 | * for the default locale. This is a convenient way to obtain a | ||
| 417 | * DecimalFormat when internationalization is not the main concern. | ||
| 418 | * <p> | ||
| 419 | * To obtain standard formats for a given locale, use the factory methods | ||
| 420 | * on NumberFormat such as getNumberInstance. These factories will | ||
| 421 | * return the most appropriate sub-class of NumberFormat for a given | ||
| 422 | * locale. | ||
| 423 | * | ||
| 424 | * @param pattern A non-localized pattern string. | ||
| 425 | * @exception NullPointerException if <code>pattern</code> is null | ||
| 426 | * @exception IllegalArgumentException if the given pattern is invalid. | ||
| 427 | * @see java.text.NumberFormat#getInstance | ||
| 428 | * @see java.text.NumberFormat#getNumberInstance | ||
| 429 | * @see java.text.NumberFormat#getCurrencyInstance | ||
| 430 | * @see java.text.NumberFormat#getPercentInstance | ||
| 431 | */ | ||
| 432 | public DecimalFormat(String pattern) { | ||
| 433 | // Always applyPattern after the symbols are set | ||
| 434 | this.symbols = new DecimalFormatSymbols(Locale.getDefault(Locale.Category.FORMAT)); | ||
| 435 | applyPattern(pattern, false); | ||
| 436 | } | ||
| 437 | |||
| 438 | |||
| 439 | /** | ||
| 440 | * Creates a DecimalFormat using the given pattern and symbols. | ||
| 441 | * Use this constructor when you need to completely customize the | ||
| 442 | * behavior of the format. | ||
| 443 | * <p> | ||
| 444 | * To obtain standard formats for a given | ||
| 445 | * locale, use the factory methods on NumberFormat such as | ||
| 446 | * getInstance or getCurrencyInstance. If you need only minor adjustments | ||
| 447 | * to a standard format, you can modify the format returned by | ||
| 448 | * a NumberFormat factory method. | ||
| 449 | * | ||
| 450 | * @param pattern a non-localized pattern string | ||
| 451 | * @param symbols the set of symbols to be used | ||
| 452 | * @exception NullPointerException if any of the given arguments is null | ||
| 453 | * @exception IllegalArgumentException if the given pattern is invalid | ||
| 454 | * @see java.text.NumberFormat#getInstance | ||
| 455 | * @see java.text.NumberFormat#getNumberInstance | ||
| 456 | * @see java.text.NumberFormat#getCurrencyInstance | ||
| 457 | * @see java.text.NumberFormat#getPercentInstance | ||
| 458 | * @see java.text.DecimalFormatSymbols | ||
| 459 | */ | ||
| 460 | public DecimalFormat (String pattern, DecimalFormatSymbols symbols) { | ||
| 461 | // Always applyPattern after the symbols are set | ||
| 462 | this.symbols = (DecimalFormatSymbols)symbols.clone(); | ||
| 463 | applyPattern(pattern, false); | ||
| 464 | } | ||
| 465 | |||
| 466 | |||
| 467 | // Overrides | ||
| 468 | /** | ||
| 469 | * Formats a number and appends the resulting text to the given string | ||
| 470 | * buffer. | ||
| 471 | * The number can be of any subclass of {@link java.lang.Number}. | ||
| 472 | * <p> | ||
| 473 | * This implementation uses the maximum precision permitted. | ||
| 474 | * @param number the number to format | ||
| 475 | * @param toAppendTo the <code>StringBuffer</code> to which the formatted | ||
| 476 | * text is to be appended | ||
| 477 | * @param pos On input: an alignment field, if desired. | ||
| 478 | * On output: the offsets of the alignment field. | ||
| 479 | * @return the value passed in as <code>toAppendTo</code> | ||
| 480 | * @exception IllegalArgumentException if <code>number</code> is | ||
| 481 | * null or not an instance of <code>Number</code>. | ||
| 482 | * @exception NullPointerException if <code>toAppendTo</code> or | ||
| 483 | * <code>pos</code> is null | ||
| 484 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 485 | * mode being set to RoundingMode.UNNECESSARY | ||
| 486 | * @see java.text.FieldPosition | ||
| 487 | */ | ||
| 488 | public final StringBuffer format(Object number, | ||
| 489 | StringBuffer toAppendTo, | ||
| 490 | FieldPosition pos) { | ||
| 491 | if (number instanceof Long || number instanceof Integer || | ||
| 492 | number instanceof Short || number instanceof Byte || | ||
| 493 | number instanceof AtomicInteger || | ||
| 494 | number instanceof AtomicLong || | ||
| 495 | (number instanceof BigInteger && | ||
| 496 | ((BigInteger)number).bitLength () < 64)) { | ||
| 497 | return format(((Number)number).longValue(), toAppendTo, pos); | ||
| 498 | } else if (number instanceof BigDecimal) { | ||
| 499 | return format((BigDecimal)number, toAppendTo, pos); | ||
| 500 | } else if (number instanceof BigInteger) { | ||
| 501 | return format((BigInteger)number, toAppendTo, pos); | ||
| 502 | } else if (number instanceof Number) { | ||
| 503 | return format(((Number)number).doubleValue(), toAppendTo, pos); | ||
| 504 | } else { | ||
| 505 | throw new IllegalArgumentException("Cannot format given Object as a Number"); | ||
| 506 | } | ||
| 507 | } | ||
| 508 | |||
| 509 | /** | ||
| 510 | * Formats a double to produce a string. | ||
| 511 | * @param number The double to format | ||
| 512 | * @param result where the text is to be appended | ||
| 513 | * @param fieldPosition On input: an alignment field, if desired. | ||
| 514 | * On output: the offsets of the alignment field. | ||
| 515 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 516 | * mode being set to RoundingMode.UNNECESSARY | ||
| 517 | * @return The formatted number string | ||
| 518 | * @see java.text.FieldPosition | ||
| 519 | */ | ||
| 520 | public StringBuffer format(double number, StringBuffer result, | ||
| 521 | FieldPosition fieldPosition) { | ||
| 522 | fieldPosition.setBeginIndex(0); | ||
| 523 | fieldPosition.setEndIndex(0); | ||
| 524 | |||
| 525 | return format(number, result, fieldPosition.getFieldDelegate()); | ||
| 526 | } | ||
| 527 | |||
| 528 | /** | ||
| 529 | * Formats a double to produce a string. | ||
| 530 | * @param number The double to format | ||
| 531 | * @param result where the text is to be appended | ||
| 532 | * @param delegate notified of locations of sub fields | ||
| 533 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 534 | * mode being set to RoundingMode.UNNECESSARY | ||
| 535 | * @return The formatted number string | ||
| 536 | */ | ||
| 537 | private StringBuffer format(double number, StringBuffer result, | ||
| 538 | FieldDelegate delegate) { | ||
| 539 | if (Double.isNaN(number) || | ||
| 540 | (Double.isInfinite(number) && multiplier == 0)) { | ||
| 541 | int iFieldStart = result.length(); | ||
| 542 | result.append(symbols.getNaN()); | ||
| 543 | delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, | ||
| 544 | iFieldStart, result.length(), result); | ||
| 545 | return result; | ||
| 546 | } | ||
| 547 | |||
| 548 | /* Detecting whether a double is negative is easy with the exception of | ||
| 549 | * the value -0.0. This is a double which has a zero mantissa (and | ||
| 550 | * exponent), but a negative sign bit. It is semantically distinct from | ||
| 551 | * a zero with a positive sign bit, and this distinction is important | ||
| 552 | * to certain kinds of computations. However, it's a little tricky to | ||
| 553 | * detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you may | ||
| 554 | * ask, does it behave distinctly from +0.0? Well, 1/(-0.0) == | ||
| 555 | * -Infinity. Proper detection of -0.0 is needed to deal with the | ||
| 556 | * issues raised by bugs 4106658, 4106667, and 4147706. Liu 7/6/98. | ||
| 557 | */ | ||
| 558 | boolean isNegative = ((number < 0.0) || (number == 0.0 && 1/number < 0.0)) ^ (multiplier < 0); | ||
| 559 | |||
| 560 | if (multiplier != 1) { | ||
| 561 | number *= multiplier; | ||
| 562 | } | ||
| 563 | |||
| 564 | if (Double.isInfinite(number)) { | ||
| 565 | if (isNegative) { | ||
| 566 | append(result, negativePrefix, delegate, | ||
| 567 | getNegativePrefixFieldPositions(), Field.SIGN); | ||
| 568 | } else { | ||
| 569 | append(result, positivePrefix, delegate, | ||
| 570 | getPositivePrefixFieldPositions(), Field.SIGN); | ||
| 571 | } | ||
| 572 | |||
| 573 | int iFieldStart = result.length(); | ||
| 574 | result.append(symbols.getInfinity()); | ||
| 575 | delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, | ||
| 576 | iFieldStart, result.length(), result); | ||
| 577 | |||
| 578 | if (isNegative) { | ||
| 579 | append(result, negativeSuffix, delegate, | ||
| 580 | getNegativeSuffixFieldPositions(), Field.SIGN); | ||
| 581 | } else { | ||
| 582 | append(result, positiveSuffix, delegate, | ||
| 583 | getPositiveSuffixFieldPositions(), Field.SIGN); | ||
| 584 | } | ||
| 585 | |||
| 586 | return result; | ||
| 587 | } | ||
| 588 | |||
| 589 | if (isNegative) { | ||
| 590 | number = -number; | ||
| 591 | } | ||
| 592 | |||
| 593 | // at this point we are guaranteed a nonnegative finite number. | ||
| 594 | assert(number >= 0 && !Double.isInfinite(number)); | ||
| 595 | |||
| 596 | synchronized(digitList) { | ||
| 597 | int maxIntDigits = super.getMaximumIntegerDigits(); | ||
| 598 | int minIntDigits = super.getMinimumIntegerDigits(); | ||
| 599 | int maxFraDigits = super.getMaximumFractionDigits(); | ||
| 600 | int minFraDigits = super.getMinimumFractionDigits(); | ||
| 601 | |||
| 602 | digitList.set(isNegative, number, useExponentialNotation ? | ||
| 603 | maxIntDigits + maxFraDigits : maxFraDigits, | ||
| 604 | !useExponentialNotation); | ||
| 605 | return subformat(result, delegate, isNegative, false, | ||
| 606 | maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); | ||
| 607 | } | ||
| 608 | } | ||
| 609 | |||
| 610 | /** | ||
| 611 | * Format a long to produce a string. | ||
| 612 | * @param number The long to format | ||
| 613 | * @param result where the text is to be appended | ||
| 614 | * @param fieldPosition On input: an alignment field, if desired. | ||
| 615 | * On output: the offsets of the alignment field. | ||
| 616 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 617 | * mode being set to RoundingMode.UNNECESSARY | ||
| 618 | * @return The formatted number string | ||
| 619 | * @see java.text.FieldPosition | ||
| 620 | */ | ||
| 621 | public StringBuffer format(long number, StringBuffer result, | ||
| 622 | FieldPosition fieldPosition) { | ||
| 623 | fieldPosition.setBeginIndex(0); | ||
| 624 | fieldPosition.setEndIndex(0); | ||
| 625 | |||
| 626 | return format(number, result, fieldPosition.getFieldDelegate()); | ||
| 627 | } | ||
| 628 | |||
| 629 | /** | ||
| 630 | * Format a long to produce a string. | ||
| 631 | * @param number The long to format | ||
| 632 | * @param result where the text is to be appended | ||
| 633 | * @param delegate notified of locations of sub fields | ||
| 634 | * @return The formatted number string | ||
| 635 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 636 | * mode being set to RoundingMode.UNNECESSARY | ||
| 637 | * @see java.text.FieldPosition | ||
| 638 | */ | ||
| 639 | private StringBuffer format(long number, StringBuffer result, | ||
| 640 | FieldDelegate delegate) { | ||
| 641 | boolean isNegative = (number < 0); | ||
| 642 | if (isNegative) { | ||
| 643 | number = -number; | ||
| 644 | } | ||
| 645 | |||
| 646 | // In general, long values always represent real finite numbers, so | ||
| 647 | // we don't have to check for +/- Infinity or NaN. However, there | ||
| 648 | // is one case we have to be careful of: The multiplier can push | ||
| 649 | // a number near MIN_VALUE or MAX_VALUE outside the legal range. We | ||
| 650 | // check for this before multiplying, and if it happens we use | ||
| 651 | // BigInteger instead. | ||
| 652 | boolean useBigInteger = false; | ||
| 653 | if (number < 0) { // This can only happen if number == Long.MIN_VALUE. | ||
| 654 | if (multiplier != 0) { | ||
| 655 | useBigInteger = true; | ||
| 656 | } | ||
| 657 | } else if (multiplier != 1 && multiplier != 0) { | ||
| 658 | long cutoff = Long.MAX_VALUE / multiplier; | ||
| 659 | if (cutoff < 0) { | ||
| 660 | cutoff = -cutoff; | ||
| 661 | } | ||
| 662 | useBigInteger = (number > cutoff); | ||
| 663 | } | ||
| 664 | |||
| 665 | if (useBigInteger) { | ||
| 666 | if (isNegative) { | ||
| 667 | number = -number; | ||
| 668 | } | ||
| 669 | BigInteger bigIntegerValue = BigInteger.valueOf(number); | ||
| 670 | return format(bigIntegerValue, result, delegate, true); | ||
| 671 | } | ||
| 672 | |||
| 673 | number *= multiplier; | ||
| 674 | if (number == 0) { | ||
| 675 | isNegative = false; | ||
| 676 | } else { | ||
| 677 | if (multiplier < 0) { | ||
| 678 | number = -number; | ||
| 679 | isNegative = !isNegative; | ||
| 680 | } | ||
| 681 | } | ||
| 682 | |||
| 683 | synchronized(digitList) { | ||
| 684 | int maxIntDigits = super.getMaximumIntegerDigits(); | ||
| 685 | int minIntDigits = super.getMinimumIntegerDigits(); | ||
| 686 | int maxFraDigits = super.getMaximumFractionDigits(); | ||
| 687 | int minFraDigits = super.getMinimumFractionDigits(); | ||
| 688 | |||
| 689 | digitList.set(isNegative, number, | ||
| 690 | useExponentialNotation ? maxIntDigits + maxFraDigits : 0); | ||
| 691 | |||
| 692 | return subformat(result, delegate, isNegative, true, | ||
| 693 | maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); | ||
| 694 | } | ||
| 695 | } | ||
| 696 | |||
| 697 | /** | ||
| 698 | * Formats a BigDecimal to produce a string. | ||
| 699 | * @param number The BigDecimal to format | ||
| 700 | * @param result where the text is to be appended | ||
| 701 | * @param fieldPosition On input: an alignment field, if desired. | ||
| 702 | * On output: the offsets of the alignment field. | ||
| 703 | * @return The formatted number string | ||
| 704 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 705 | * mode being set to RoundingMode.UNNECESSARY | ||
| 706 | * @see java.text.FieldPosition | ||
| 707 | */ | ||
| 708 | private StringBuffer format(BigDecimal number, StringBuffer result, | ||
| 709 | FieldPosition fieldPosition) { | ||
| 710 | fieldPosition.setBeginIndex(0); | ||
| 711 | fieldPosition.setEndIndex(0); | ||
| 712 | return format(number, result, fieldPosition.getFieldDelegate()); | ||
| 713 | } | ||
| 714 | |||
| 715 | /** | ||
| 716 | * Formats a BigDecimal to produce a string. | ||
| 717 | * @param number The BigDecimal to format | ||
| 718 | * @param result where the text is to be appended | ||
| 719 | * @param delegate notified of locations of sub fields | ||
| 720 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 721 | * mode being set to RoundingMode.UNNECESSARY | ||
| 722 | * @return The formatted number string | ||
| 723 | */ | ||
| 724 | private StringBuffer format(BigDecimal number, StringBuffer result, | ||
| 725 | FieldDelegate delegate) { | ||
| 726 | if (multiplier != 1) { | ||
| 727 | number = number.multiply(getBigDecimalMultiplier()); | ||
| 728 | } | ||
| 729 | boolean isNegative = number.signum() == -1; | ||
| 730 | if (isNegative) { | ||
| 731 | number = number.negate(); | ||
| 732 | } | ||
| 733 | |||
| 734 | synchronized(digitList) { | ||
| 735 | int maxIntDigits = getMaximumIntegerDigits(); | ||
| 736 | int minIntDigits = getMinimumIntegerDigits(); | ||
| 737 | int maxFraDigits = getMaximumFractionDigits(); | ||
| 738 | int minFraDigits = getMinimumFractionDigits(); | ||
| 739 | int maximumDigits = maxIntDigits + maxFraDigits; | ||
| 740 | |||
| 741 | digitList.set(isNegative, number, useExponentialNotation ? | ||
| 742 | ((maximumDigits < 0) ? Integer.MAX_VALUE : maximumDigits) : | ||
| 743 | maxFraDigits, !useExponentialNotation); | ||
| 744 | |||
| 745 | return subformat(result, delegate, isNegative, false, | ||
| 746 | maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); | ||
| 747 | } | ||
| 748 | } | ||
| 749 | |||
| 750 | /** | ||
| 751 | * Format a BigInteger to produce a string. | ||
| 752 | * @param number The BigInteger to format | ||
| 753 | * @param result where the text is to be appended | ||
| 754 | * @param fieldPosition On input: an alignment field, if desired. | ||
| 755 | * On output: the offsets of the alignment field. | ||
| 756 | * @return The formatted number string | ||
| 757 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 758 | * mode being set to RoundingMode.UNNECESSARY | ||
| 759 | * @see java.text.FieldPosition | ||
| 760 | */ | ||
| 761 | private StringBuffer format(BigInteger number, StringBuffer result, | ||
| 762 | FieldPosition fieldPosition) { | ||
| 763 | fieldPosition.setBeginIndex(0); | ||
| 764 | fieldPosition.setEndIndex(0); | ||
| 765 | |||
| 766 | return format(number, result, fieldPosition.getFieldDelegate(), false); | ||
| 767 | } | ||
| 768 | |||
| 769 | /** | ||
| 770 | * Format a BigInteger to produce a string. | ||
| 771 | * @param number The BigInteger to format | ||
| 772 | * @param result where the text is to be appended | ||
| 773 | * @param delegate notified of locations of sub fields | ||
| 774 | * @return The formatted number string | ||
| 775 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 776 | * mode being set to RoundingMode.UNNECESSARY | ||
| 777 | * @see java.text.FieldPosition | ||
| 778 | */ | ||
| 779 | private StringBuffer format(BigInteger number, StringBuffer result, | ||
| 780 | FieldDelegate delegate, boolean formatLong) { | ||
| 781 | if (multiplier != 1) { | ||
| 782 | number = number.multiply(getBigIntegerMultiplier()); | ||
| 783 | } | ||
| 784 | boolean isNegative = number.signum() == -1; | ||
| 785 | if (isNegative) { | ||
| 786 | number = number.negate(); | ||
| 787 | } | ||
| 788 | |||
| 789 | synchronized(digitList) { | ||
| 790 | int maxIntDigits, minIntDigits, maxFraDigits, minFraDigits, maximumDigits; | ||
| 791 | if (formatLong) { | ||
| 792 | maxIntDigits = super.getMaximumIntegerDigits(); | ||
| 793 | minIntDigits = super.getMinimumIntegerDigits(); | ||
| 794 | maxFraDigits = super.getMaximumFractionDigits(); | ||
| 795 | minFraDigits = super.getMinimumFractionDigits(); | ||
| 796 | maximumDigits = maxIntDigits + maxFraDigits; | ||
| 797 | } else { | ||
| 798 | maxIntDigits = getMaximumIntegerDigits(); | ||
| 799 | minIntDigits = getMinimumIntegerDigits(); | ||
| 800 | maxFraDigits = getMaximumFractionDigits(); | ||
| 801 | minFraDigits = getMinimumFractionDigits(); | ||
| 802 | maximumDigits = maxIntDigits + maxFraDigits; | ||
| 803 | if (maximumDigits < 0) { | ||
| 804 | maximumDigits = Integer.MAX_VALUE; | ||
| 805 | } | ||
| 806 | } | ||
| 807 | |||
| 808 | digitList.set(isNegative, number, | ||
| 809 | useExponentialNotation ? maximumDigits : 0); | ||
| 810 | |||
| 811 | return subformat(result, delegate, isNegative, true, | ||
| 812 | maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); | ||
| 813 | } | ||
| 814 | } | ||
| 815 | |||
| 816 | /** | ||
| 817 | * Formats an Object producing an <code>AttributedCharacterIterator</code>. | ||
| 818 | * You can use the returned <code>AttributedCharacterIterator</code> | ||
| 819 | * to build the resulting String, as well as to determine information | ||
| 820 | * about the resulting String. | ||
| 821 | * <p> | ||
| 822 | * Each attribute key of the AttributedCharacterIterator will be of type | ||
| 823 | * <code>NumberFormat.Field</code>, with the attribute value being the | ||
| 824 | * same as the attribute key. | ||
| 825 | * | ||
| 826 | * @exception NullPointerException if obj is null. | ||
| 827 | * @exception IllegalArgumentException when the Format cannot format the | ||
| 828 | * given object. | ||
| 829 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 830 | * mode being set to RoundingMode.UNNECESSARY | ||
| 831 | * @param obj The object to format | ||
| 832 | * @return AttributedCharacterIterator describing the formatted value. | ||
| 833 | * @since 1.4 | ||
| 834 | */ | ||
| 835 | public AttributedCharacterIterator formatToCharacterIterator(Object obj) { | ||
| 836 | CharacterIteratorFieldDelegate delegate = | ||
| 837 | new CharacterIteratorFieldDelegate(); | ||
| 838 | StringBuffer sb = new StringBuffer(); | ||
| 839 | |||
| 840 | if (obj instanceof Double || obj instanceof Float) { | ||
| 841 | format(((Number)obj).doubleValue(), sb, delegate); | ||
| 842 | } else if (obj instanceof Long || obj instanceof Integer || | ||
| 843 | obj instanceof Short || obj instanceof Byte || | ||
| 844 | obj instanceof AtomicInteger || obj instanceof AtomicLong) { | ||
| 845 | format(((Number)obj).longValue(), sb, delegate); | ||
| 846 | } else if (obj instanceof BigDecimal) { | ||
| 847 | format((BigDecimal)obj, sb, delegate); | ||
| 848 | } else if (obj instanceof BigInteger) { | ||
| 849 | format((BigInteger)obj, sb, delegate, false); | ||
| 850 | } else if (obj == null) { | ||
| 851 | throw new NullPointerException( | ||
| 852 | "formatToCharacterIterator must be passed non-null object"); | ||
| 853 | } else { | ||
| 854 | throw new IllegalArgumentException( | ||
| 855 | "Cannot format given Object as a Number"); | ||
| 856 | } | ||
| 857 | return delegate.getIterator(sb.toString()); | ||
| 858 | } | ||
| 859 | |||
| 860 | /** | ||
| 861 | * Complete the formatting of a finite number. On entry, the digitList must | ||
| 862 | * be filled in with the correct digits. | ||
| 863 | */ | ||
| 864 | private StringBuffer subformat(StringBuffer result, FieldDelegate delegate, | ||
| 865 | boolean isNegative, boolean isInteger, | ||
| 866 | int maxIntDigits, int minIntDigits, | ||
| 867 | int maxFraDigits, int minFraDigits) { | ||
| 868 | // NOTE: This isn't required anymore because DigitList takes care of this. | ||
| 869 | // | ||
| 870 | // // The negative of the exponent represents the number of leading | ||
| 871 | // // zeros between the decimal and the first non-zero digit, for | ||
| 872 | // // a value < 0.1 (e.g., for 0.00123, -fExponent == 2). If this | ||
| 873 | // // is more than the maximum fraction digits, then we have an underflow | ||
| 874 | // // for the printed representation. We recognize this here and set | ||
| 875 | // // the DigitList representation to zero in this situation. | ||
| 876 | // | ||
| 877 | // if (-digitList.decimalAt >= getMaximumFractionDigits()) | ||
| 878 | // { | ||
| 879 | // digitList.count = 0; | ||
| 880 | // } | ||
| 881 | |||
| 882 | char zero = symbols.getZeroDigit(); | ||
| 883 | int zeroDelta = zero - '0'; // '0' is the DigitList representation of zero | ||
| 884 | char grouping = symbols.getGroupingSeparator(); | ||
| 885 | char decimal = isCurrencyFormat ? | ||
| 886 | symbols.getMonetaryDecimalSeparator() : | ||
| 887 | symbols.getDecimalSeparator(); | ||
| 888 | |||
| 889 | /* Per bug 4147706, DecimalFormat must respect the sign of numbers which | ||
| 890 | * format as zero. This allows sensible computations and preserves | ||
| 891 | * relations such as signum(1/x) = signum(x), where x is +Infinity or | ||
| 892 | * -Infinity. Prior to this fix, we always formatted zero values as if | ||
| 893 | * they were positive. Liu 7/6/98. | ||
| 894 | */ | ||
| 895 | if (digitList.isZero()) { | ||
| 896 | digitList.decimalAt = 0; // Normalize | ||
| 897 | } | ||
| 898 | |||
| 899 | if (isNegative) { | ||
| 900 | append(result, negativePrefix, delegate, | ||
| 901 | getNegativePrefixFieldPositions(), Field.SIGN); | ||
| 902 | } else { | ||
| 903 | append(result, positivePrefix, delegate, | ||
| 904 | getPositivePrefixFieldPositions(), Field.SIGN); | ||
| 905 | } | ||
| 906 | |||
| 907 | if (useExponentialNotation) { | ||
| 908 | int iFieldStart = result.length(); | ||
| 909 | int iFieldEnd = -1; | ||
| 910 | int fFieldStart = -1; | ||
| 911 | |||
| 912 | // Minimum integer digits are handled in exponential format by | ||
| 913 | // adjusting the exponent. For example, 0.01234 with 3 minimum | ||
| 914 | // integer digits is "123.4E-4". | ||
| 915 | |||
| 916 | // Maximum integer digits are interpreted as indicating the | ||
| 917 | // repeating range. This is useful for engineering notation, in | ||
| 918 | // which the exponent is restricted to a multiple of 3. For | ||
| 919 | // example, 0.01234 with 3 maximum integer digits is "12.34e-3". | ||
| 920 | // If maximum integer digits are > 1 and are larger than | ||
| 921 | // minimum integer digits, then minimum integer digits are | ||
| 922 | // ignored. | ||
| 923 | int exponent = digitList.decimalAt; | ||
| 924 | int repeat = maxIntDigits; | ||
| 925 | int minimumIntegerDigits = minIntDigits; | ||
| 926 | if (repeat > 1 && repeat > minIntDigits) { | ||
| 927 | // A repeating range is defined; adjust to it as follows. | ||
| 928 | // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3; | ||
| 929 | // -3,-4,-5=>-6, etc. This takes into account that the | ||
| 930 | // exponent we have here is off by one from what we expect; | ||
| 931 | // it is for the format 0.MMMMMx10^n. | ||
| 932 | if (exponent >= 1) { | ||
| 933 | exponent = ((exponent - 1) / repeat) * repeat; | ||
| 934 | } else { | ||
| 935 | // integer division rounds towards 0 | ||
| 936 | exponent = ((exponent - repeat) / repeat) * repeat; | ||
| 937 | } | ||
| 938 | minimumIntegerDigits = 1; | ||
| 939 | } else { | ||
| 940 | // No repeating range is defined; use minimum integer digits. | ||
| 941 | exponent -= minimumIntegerDigits; | ||
| 942 | } | ||
| 943 | |||
| 944 | // We now output a minimum number of digits, and more if there | ||
| 945 | // are more digits, up to the maximum number of digits. We | ||
| 946 | // place the decimal point after the "integer" digits, which | ||
| 947 | // are the first (decimalAt - exponent) digits. | ||
| 948 | int minimumDigits = minIntDigits + minFraDigits; | ||
| 949 | if (minimumDigits < 0) { // overflow? | ||
| 950 | minimumDigits = Integer.MAX_VALUE; | ||
| 951 | } | ||
| 952 | |||
| 953 | // The number of integer digits is handled specially if the number | ||
| 954 | // is zero, since then there may be no digits. | ||
| 955 | int integerDigits = digitList.isZero() ? minimumIntegerDigits : | ||
| 956 | digitList.decimalAt - exponent; | ||
| 957 | if (minimumDigits < integerDigits) { | ||
| 958 | minimumDigits = integerDigits; | ||
| 959 | } | ||
| 960 | int totalDigits = digitList.count; | ||
| 961 | if (minimumDigits > totalDigits) { | ||
| 962 | totalDigits = minimumDigits; | ||
| 963 | } | ||
| 964 | boolean addedDecimalSeparator = false; | ||
| 965 | |||
| 966 | for (int i=0; i<totalDigits; ++i) { | ||
| 967 | if (i == integerDigits) { | ||
| 968 | // Record field information for caller. | ||
| 969 | iFieldEnd = result.length(); | ||
| 970 | |||
| 971 | result.append(decimal); | ||
| 972 | addedDecimalSeparator = true; | ||
| 973 | |||
| 974 | // Record field information for caller. | ||
| 975 | fFieldStart = result.length(); | ||
| 976 | } | ||
| 977 | result.append((i < digitList.count) ? | ||
| 978 | (char)(digitList.digits[i] + zeroDelta) : | ||
| 979 | zero); | ||
| 980 | } | ||
| 981 | |||
| 982 | if (decimalSeparatorAlwaysShown && totalDigits == integerDigits) { | ||
| 983 | // Record field information for caller. | ||
| 984 | iFieldEnd = result.length(); | ||
| 985 | |||
| 986 | result.append(decimal); | ||
| 987 | addedDecimalSeparator = true; | ||
| 988 | |||
| 989 | // Record field information for caller. | ||
| 990 | fFieldStart = result.length(); | ||
| 991 | } | ||
| 992 | |||
| 993 | // Record field information | ||
| 994 | if (iFieldEnd == -1) { | ||
| 995 | iFieldEnd = result.length(); | ||
| 996 | } | ||
| 997 | delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, | ||
| 998 | iFieldStart, iFieldEnd, result); | ||
| 999 | if (addedDecimalSeparator) { | ||
| 1000 | delegate.formatted(Field.DECIMAL_SEPARATOR, | ||
| 1001 | Field.DECIMAL_SEPARATOR, | ||
| 1002 | iFieldEnd, fFieldStart, result); | ||
| 1003 | } | ||
| 1004 | if (fFieldStart == -1) { | ||
| 1005 | fFieldStart = result.length(); | ||
| 1006 | } | ||
| 1007 | delegate.formatted(FRACTION_FIELD, Field.FRACTION, Field.FRACTION, | ||
| 1008 | fFieldStart, result.length(), result); | ||
| 1009 | |||
| 1010 | // The exponent is output using the pattern-specified minimum | ||
| 1011 | // exponent digits. There is no maximum limit to the exponent | ||
| 1012 | // digits, since truncating the exponent would result in an | ||
| 1013 | // unacceptable inaccuracy. | ||
| 1014 | int fieldStart = result.length(); | ||
| 1015 | |||
| 1016 | result.append(symbols.getExponentSeparator()); | ||
| 1017 | |||
| 1018 | delegate.formatted(Field.EXPONENT_SYMBOL, Field.EXPONENT_SYMBOL, | ||
| 1019 | fieldStart, result.length(), result); | ||
| 1020 | |||
| 1021 | // For zero values, we force the exponent to zero. We | ||
| 1022 | // must do this here, and not earlier, because the value | ||
| 1023 | // is used to determine integer digit count above. | ||
| 1024 | if (digitList.isZero()) { | ||
| 1025 | exponent = 0; | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | boolean negativeExponent = exponent < 0; | ||
| 1029 | if (negativeExponent) { | ||
| 1030 | exponent = -exponent; | ||
| 1031 | fieldStart = result.length(); | ||
| 1032 | result.append(symbols.getMinusSign()); | ||
| 1033 | delegate.formatted(Field.EXPONENT_SIGN, Field.EXPONENT_SIGN, | ||
| 1034 | fieldStart, result.length(), result); | ||
| 1035 | } | ||
| 1036 | digitList.set(negativeExponent, exponent); | ||
| 1037 | |||
| 1038 | int eFieldStart = result.length(); | ||
| 1039 | |||
| 1040 | for (int i=digitList.decimalAt; i<minExponentDigits; ++i) { | ||
| 1041 | result.append(zero); | ||
| 1042 | } | ||
| 1043 | for (int i=0; i<digitList.decimalAt; ++i) { | ||
| 1044 | result.append((i < digitList.count) ? | ||
| 1045 | (char)(digitList.digits[i] + zeroDelta) : zero); | ||
| 1046 | } | ||
| 1047 | delegate.formatted(Field.EXPONENT, Field.EXPONENT, eFieldStart, | ||
| 1048 | result.length(), result); | ||
| 1049 | } else { | ||
| 1050 | int iFieldStart = result.length(); | ||
| 1051 | |||
| 1052 | // Output the integer portion. Here 'count' is the total | ||
| 1053 | // number of integer digits we will display, including both | ||
| 1054 | // leading zeros required to satisfy getMinimumIntegerDigits, | ||
| 1055 | // and actual digits present in the number. | ||
| 1056 | int count = minIntDigits; | ||
| 1057 | int digitIndex = 0; // Index into digitList.fDigits[] | ||
| 1058 | if (digitList.decimalAt > 0 && count < digitList.decimalAt) { | ||
| 1059 | count = digitList.decimalAt; | ||
| 1060 | } | ||
| 1061 | |||
| 1062 | // Handle the case where getMaximumIntegerDigits() is smaller | ||
| 1063 | // than the real number of integer digits. If this is so, we | ||
| 1064 | // output the least significant max integer digits. For example, | ||
| 1065 | // the value 1997 printed with 2 max integer digits is just "97". | ||
| 1066 | if (count > maxIntDigits) { | ||
| 1067 | count = maxIntDigits; | ||
| 1068 | digitIndex = digitList.decimalAt - count; | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | int sizeBeforeIntegerPart = result.length(); | ||
| 1072 | for (int i=count-1; i>=0; --i) { | ||
| 1073 | if (i < digitList.decimalAt && digitIndex < digitList.count) { | ||
| 1074 | // Output a real digit | ||
| 1075 | result.append((char)(digitList.digits[digitIndex++] + zeroDelta)); | ||
| 1076 | } else { | ||
| 1077 | // Output a leading zero | ||
| 1078 | result.append(zero); | ||
| 1079 | } | ||
| 1080 | |||
| 1081 | // Output grouping separator if necessary. Don't output a | ||
| 1082 | // grouping separator if i==0 though; that's at the end of | ||
| 1083 | // the integer part. | ||
| 1084 | if (isGroupingUsed() && i>0 && (groupingSize != 0) && | ||
| 1085 | (i % groupingSize == 0)) { | ||
| 1086 | int gStart = result.length(); | ||
| 1087 | result.append(grouping); | ||
| 1088 | delegate.formatted(Field.GROUPING_SEPARATOR, | ||
| 1089 | Field.GROUPING_SEPARATOR, gStart, | ||
| 1090 | result.length(), result); | ||
| 1091 | } | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | // Determine whether or not there are any printable fractional | ||
| 1095 | // digits. If we've used up the digits we know there aren't. | ||
| 1096 | boolean fractionPresent = (minFraDigits > 0) || | ||
| 1097 | (!isInteger && digitIndex < digitList.count); | ||
| 1098 | |||
| 1099 | // If there is no fraction present, and we haven't printed any | ||
| 1100 | // integer digits, then print a zero. Otherwise we won't print | ||
| 1101 | // _any_ digits, and we won't be able to parse this string. | ||
| 1102 | if (!fractionPresent && result.length() == sizeBeforeIntegerPart) { | ||
| 1103 | result.append(zero); | ||
| 1104 | } | ||
| 1105 | |||
| 1106 | delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, | ||
| 1107 | iFieldStart, result.length(), result); | ||
| 1108 | |||
| 1109 | // Output the decimal separator if we always do so. | ||
| 1110 | int sStart = result.length(); | ||
| 1111 | if (decimalSeparatorAlwaysShown || fractionPresent) { | ||
| 1112 | result.append(decimal); | ||
| 1113 | } | ||
| 1114 | |||
| 1115 | if (sStart != result.length()) { | ||
| 1116 | delegate.formatted(Field.DECIMAL_SEPARATOR, | ||
| 1117 | Field.DECIMAL_SEPARATOR, | ||
| 1118 | sStart, result.length(), result); | ||
| 1119 | } | ||
| 1120 | int fFieldStart = result.length(); | ||
| 1121 | |||
| 1122 | for (int i=0; i < maxFraDigits; ++i) { | ||
| 1123 | // Here is where we escape from the loop. We escape if we've | ||
| 1124 | // output the maximum fraction digits (specified in the for | ||
| 1125 | // expression above). | ||
| 1126 | // We also stop when we've output the minimum digits and either: | ||
| 1127 | // we have an integer, so there is no fractional stuff to | ||
| 1128 | // display, or we're out of significant digits. | ||
| 1129 | if (i >= minFraDigits && | ||
| 1130 | (isInteger || digitIndex >= digitList.count)) { | ||
| 1131 | break; | ||
| 1132 | } | ||
| 1133 | |||
| 1134 | // Output leading fractional zeros. These are zeros that come | ||
| 1135 | // after the decimal but before any significant digits. These | ||
| 1136 | // are only output if abs(number being formatted) < 1.0. | ||
| 1137 | if (-1-i > (digitList.decimalAt-1)) { | ||
| 1138 | result.append(zero); | ||
| 1139 | continue; | ||
| 1140 | } | ||
| 1141 | |||
| 1142 | // Output a digit, if we have any precision left, or a | ||
| 1143 | // zero if we don't. We don't want to output noise digits. | ||
| 1144 | if (!isInteger && digitIndex < digitList.count) { | ||
| 1145 | result.append((char)(digitList.digits[digitIndex++] + zeroDelta)); | ||
| 1146 | } else { | ||
| 1147 | result.append(zero); | ||
| 1148 | } | ||
| 1149 | } | ||
| 1150 | |||
| 1151 | // Record field information for caller. | ||
| 1152 | delegate.formatted(FRACTION_FIELD, Field.FRACTION, Field.FRACTION, | ||
| 1153 | fFieldStart, result.length(), result); | ||
| 1154 | } | ||
| 1155 | |||
| 1156 | if (isNegative) { | ||
| 1157 | append(result, negativeSuffix, delegate, | ||
| 1158 | getNegativeSuffixFieldPositions(), Field.SIGN); | ||
| 1159 | } | ||
| 1160 | else { | ||
| 1161 | append(result, positiveSuffix, delegate, | ||
| 1162 | getPositiveSuffixFieldPositions(), Field.SIGN); | ||
| 1163 | } | ||
| 1164 | |||
| 1165 | return result; | ||
| 1166 | } | ||
| 1167 | |||
| 1168 | /** | ||
| 1169 | * Appends the String <code>string</code> to <code>result</code>. | ||
| 1170 | * <code>delegate</code> is notified of all the | ||
| 1171 | * <code>FieldPosition</code>s in <code>positions</code>. | ||
| 1172 | * <p> | ||
| 1173 | * If one of the <code>FieldPosition</code>s in <code>positions</code> | ||
| 1174 | * identifies a <code>SIGN</code> attribute, it is mapped to | ||
| 1175 | * <code>signAttribute</code>. This is used | ||
| 1176 | * to map the <code>SIGN</code> attribute to the <code>EXPONENT</code> | ||
| 1177 | * attribute as necessary. | ||
| 1178 | * <p> | ||
| 1179 | * This is used by <code>subformat</code> to add the prefix/suffix. | ||
| 1180 | */ | ||
| 1181 | private void append(StringBuffer result, String string, | ||
| 1182 | FieldDelegate delegate, | ||
| 1183 | FieldPosition[] positions, | ||
| 1184 | Format.Field signAttribute) { | ||
| 1185 | int start = result.length(); | ||
| 1186 | |||
| 1187 | if (string.length() > 0) { | ||
| 1188 | result.append(string); | ||
| 1189 | for (int counter = 0, max = positions.length; counter < max; | ||
| 1190 | counter++) { | ||
| 1191 | FieldPosition fp = positions[counter]; | ||
| 1192 | Format.Field attribute = fp.getFieldAttribute(); | ||
| 1193 | |||
| 1194 | if (attribute == Field.SIGN) { | ||
| 1195 | attribute = signAttribute; | ||
| 1196 | } | ||
| 1197 | delegate.formatted(attribute, attribute, | ||
| 1198 | start + fp.getBeginIndex(), | ||
| 1199 | start + fp.getEndIndex(), result); | ||
| 1200 | } | ||
| 1201 | } | ||
| 1202 | } | ||
| 1203 | |||
| 1204 | /** | ||
| 1205 | * Parses text from a string to produce a <code>Number</code>. | ||
| 1206 | * <p> | ||
| 1207 | * The method attempts to parse text starting at the index given by | ||
| 1208 | * <code>pos</code>. | ||
| 1209 | * If parsing succeeds, then the index of <code>pos</code> is updated | ||
| 1210 | * to the index after the last character used (parsing does not necessarily | ||
| 1211 | * use all characters up to the end of the string), and the parsed | ||
| 1212 | * number is returned. The updated <code>pos</code> can be used to | ||
| 1213 | * indicate the starting point for the next call to this method. | ||
| 1214 | * If an error occurs, then the index of <code>pos</code> is not | ||
| 1215 | * changed, the error index of <code>pos</code> is set to the index of | ||
| 1216 | * the character where the error occurred, and null is returned. | ||
| 1217 | * <p> | ||
| 1218 | * The subclass returned depends on the value of {@link #isParseBigDecimal} | ||
| 1219 | * as well as on the string being parsed. | ||
| 1220 | * <ul> | ||
| 1221 | * <li>If <code>isParseBigDecimal()</code> is false (the default), | ||
| 1222 | * most integer values are returned as <code>Long</code> | ||
| 1223 | * objects, no matter how they are written: <code>"17"</code> and | ||
| 1224 | * <code>"17.000"</code> both parse to <code>Long(17)</code>. | ||
| 1225 | * Values that cannot fit into a <code>Long</code> are returned as | ||
| 1226 | * <code>Double</code>s. This includes values with a fractional part, | ||
| 1227 | * infinite values, <code>NaN</code>, and the value -0.0. | ||
| 1228 | * <code>DecimalFormat</code> does <em>not</em> decide whether to | ||
| 1229 | * return a <code>Double</code> or a <code>Long</code> based on the | ||
| 1230 | * presence of a decimal separator in the source string. Doing so | ||
| 1231 | * would prevent integers that overflow the mantissa of a double, | ||
| 1232 | * such as <code>"-9,223,372,036,854,775,808.00"</code>, from being | ||
| 1233 | * parsed accurately. | ||
| 1234 | * <p> | ||
| 1235 | * Callers may use the <code>Number</code> methods | ||
| 1236 | * <code>doubleValue</code>, <code>longValue</code>, etc., to obtain | ||
| 1237 | * the type they want. | ||
| 1238 | * <li>If <code>isParseBigDecimal()</code> is true, values are returned | ||
| 1239 | * as <code>BigDecimal</code> objects. The values are the ones | ||
| 1240 | * constructed by {@link java.math.BigDecimal#BigDecimal(String)} | ||
| 1241 | * for corresponding strings in locale-independent format. The | ||
| 1242 | * special cases negative and positive infinity and NaN are returned | ||
| 1243 | * as <code>Double</code> instances holding the values of the | ||
| 1244 | * corresponding <code>Double</code> constants. | ||
| 1245 | * </ul> | ||
| 1246 | * <p> | ||
| 1247 | * <code>DecimalFormat</code> parses all Unicode characters that represent | ||
| 1248 | * decimal digits, as defined by <code>Character.digit()</code>. In | ||
| 1249 | * addition, <code>DecimalFormat</code> also recognizes as digits the ten | ||
| 1250 | * consecutive characters starting with the localized zero digit defined in | ||
| 1251 | * the <code>DecimalFormatSymbols</code> object. | ||
| 1252 | * | ||
| 1253 | * @param text the string to be parsed | ||
| 1254 | * @param pos A <code>ParsePosition</code> object with index and error | ||
| 1255 | * index information as described above. | ||
| 1256 | * @return the parsed value, or <code>null</code> if the parse fails | ||
| 1257 | * @exception NullPointerException if <code>text</code> or | ||
| 1258 | * <code>pos</code> is null. | ||
| 1259 | */ | ||
| 1260 | public Number parse(String text, ParsePosition pos) { | ||
| 1261 | // special case NaN | ||
| 1262 | if (text.regionMatches(pos.index, symbols.getNaN(), 0, symbols.getNaN().length())) { | ||
| 1263 | pos.index = pos.index + symbols.getNaN().length(); | ||
| 1264 | return new Double(Double.NaN); | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | boolean[] status = new boolean[STATUS_LENGTH]; | ||
| 1268 | if (!subparse(text, pos, positivePrefix, negativePrefix, digitList, false, status)) { | ||
| 1269 | return null; | ||
| 1270 | } | ||
| 1271 | |||
| 1272 | // special case INFINITY | ||
| 1273 | if (status[STATUS_INFINITE]) { | ||
| 1274 | if (status[STATUS_POSITIVE] == (multiplier >= 0)) { | ||
| 1275 | return new Double(Double.POSITIVE_INFINITY); | ||
| 1276 | } else { | ||
| 1277 | return new Double(Double.NEGATIVE_INFINITY); | ||
| 1278 | } | ||
| 1279 | } | ||
| 1280 | |||
| 1281 | if (multiplier == 0) { | ||
| 1282 | if (digitList.isZero()) { | ||
| 1283 | return new Double(Double.NaN); | ||
| 1284 | } else if (status[STATUS_POSITIVE]) { | ||
| 1285 | return new Double(Double.POSITIVE_INFINITY); | ||
| 1286 | } else { | ||
| 1287 | return new Double(Double.NEGATIVE_INFINITY); | ||
| 1288 | } | ||
| 1289 | } | ||
| 1290 | |||
| 1291 | if (isParseBigDecimal()) { | ||
| 1292 | BigDecimal bigDecimalResult = digitList.getBigDecimal(); | ||
| 1293 | |||
| 1294 | if (multiplier != 1) { | ||
| 1295 | try { | ||
| 1296 | bigDecimalResult = bigDecimalResult.divide(getBigDecimalMultiplier()); | ||
| 1297 | } | ||
| 1298 | catch (ArithmeticException e) { // non-terminating decimal expansion | ||
| 1299 | bigDecimalResult = bigDecimalResult.divide(getBigDecimalMultiplier(), roundingMode); | ||
| 1300 | } | ||
| 1301 | } | ||
| 1302 | |||
| 1303 | if (!status[STATUS_POSITIVE]) { | ||
| 1304 | bigDecimalResult = bigDecimalResult.negate(); | ||
| 1305 | } | ||
| 1306 | return bigDecimalResult; | ||
| 1307 | } else { | ||
| 1308 | boolean gotDouble = true; | ||
| 1309 | boolean gotLongMinimum = false; | ||
| 1310 | double doubleResult = 0.0; | ||
| 1311 | long longResult = 0; | ||
| 1312 | |||
| 1313 | // Finally, have DigitList parse the digits into a value. | ||
| 1314 | if (digitList.fitsIntoLong(status[STATUS_POSITIVE], isParseIntegerOnly())) { | ||
| 1315 | gotDouble = false; | ||
| 1316 | longResult = digitList.getLong(); | ||
| 1317 | if (longResult < 0) { // got Long.MIN_VALUE | ||
| 1318 | gotLongMinimum = true; | ||
| 1319 | } | ||
| 1320 | } else { | ||
| 1321 | doubleResult = digitList.getDouble(); | ||
| 1322 | } | ||
| 1323 | |||
| 1324 | // Divide by multiplier. We have to be careful here not to do | ||
| 1325 | // unneeded conversions between double and long. | ||
| 1326 | if (multiplier != 1) { | ||
| 1327 | if (gotDouble) { | ||
| 1328 | doubleResult /= multiplier; | ||
| 1329 | } else { | ||
| 1330 | // Avoid converting to double if we can | ||
| 1331 | if (longResult % multiplier == 0) { | ||
| 1332 | longResult /= multiplier; | ||
| 1333 | } else { | ||
| 1334 | doubleResult = ((double)longResult) / multiplier; | ||
| 1335 | gotDouble = true; | ||
| 1336 | } | ||
| 1337 | } | ||
| 1338 | } | ||
| 1339 | |||
| 1340 | if (!status[STATUS_POSITIVE] && !gotLongMinimum) { | ||
| 1341 | doubleResult = -doubleResult; | ||
| 1342 | longResult = -longResult; | ||
| 1343 | } | ||
| 1344 | |||
| 1345 | // At this point, if we divided the result by the multiplier, the | ||
| 1346 | // result may fit into a long. We check for this case and return | ||
| 1347 | // a long if possible. | ||
| 1348 | // We must do this AFTER applying the negative (if appropriate) | ||
| 1349 | // in order to handle the case of LONG_MIN; otherwise, if we do | ||
| 1350 | // this with a positive value -LONG_MIN, the double is > 0, but | ||
| 1351 | // the long is < 0. We also must retain a double in the case of | ||
| 1352 | // -0.0, which will compare as == to a long 0 cast to a double | ||
| 1353 | // (bug 4162852). | ||
| 1354 | if (multiplier != 1 && gotDouble) { | ||
| 1355 | longResult = (long)doubleResult; | ||
| 1356 | gotDouble = ((doubleResult != (double)longResult) || | ||
| 1357 | (doubleResult == 0.0 && 1/doubleResult < 0.0)) && | ||
| 1358 | !isParseIntegerOnly(); | ||
| 1359 | } | ||
| 1360 | |||
| 1361 | return gotDouble ? | ||
| 1362 | (Number)new Double(doubleResult) : (Number)new Long(longResult); | ||
| 1363 | } | ||
| 1364 | } | ||
| 1365 | |||
| 1366 | /** | ||
| 1367 | * Return a BigInteger multiplier. | ||
| 1368 | */ | ||
| 1369 | private BigInteger getBigIntegerMultiplier() { | ||
| 1370 | if (bigIntegerMultiplier == null) { | ||
| 1371 | bigIntegerMultiplier = BigInteger.valueOf(multiplier); | ||
| 1372 | } | ||
| 1373 | return bigIntegerMultiplier; | ||
| 1374 | } | ||
| 1375 | private transient BigInteger bigIntegerMultiplier; | ||
| 1376 | |||
| 1377 | /** | ||
| 1378 | * Return a BigDecimal multiplier. | ||
| 1379 | */ | ||
| 1380 | private BigDecimal getBigDecimalMultiplier() { | ||
| 1381 | if (bigDecimalMultiplier == null) { | ||
| 1382 | bigDecimalMultiplier = new BigDecimal(multiplier); | ||
| 1383 | } | ||
| 1384 | return bigDecimalMultiplier; | ||
| 1385 | } | ||
| 1386 | private transient BigDecimal bigDecimalMultiplier; | ||
| 1387 | |||
| 1388 | private static final int STATUS_INFINITE = 0; | ||
| 1389 | private static final int STATUS_POSITIVE = 1; | ||
| 1390 | private static final int STATUS_LENGTH = 2; | ||
| 1391 | |||
| 1392 | /** | ||
| 1393 | * Parse the given text into a number. The text is parsed beginning at | ||
| 1394 | * parsePosition, until an unparseable character is seen. | ||
| 1395 | * @param text The string to parse. | ||
| 1396 | * @param parsePosition The position at which to being parsing. Upon | ||
| 1397 | * return, the first unparseable character. | ||
| 1398 | * @param digits The DigitList to set to the parsed value. | ||
| 1399 | * @param isExponent If true, parse an exponent. This means no | ||
| 1400 | * infinite values and integer only. | ||
| 1401 | * @param status Upon return contains boolean status flags indicating | ||
| 1402 | * whether the value was infinite and whether it was positive. | ||
| 1403 | */ | ||
| 1404 | private final boolean subparse(String text, ParsePosition parsePosition, | ||
| 1405 | String positivePrefix, String negativePrefix, | ||
| 1406 | DigitList digits, boolean isExponent, | ||
| 1407 | boolean status[]) { | ||
| 1408 | int position = parsePosition.index; | ||
| 1409 | int oldStart = parsePosition.index; | ||
| 1410 | int backup; | ||
| 1411 | boolean gotPositive, gotNegative; | ||
| 1412 | |||
| 1413 | // check for positivePrefix; take longest | ||
| 1414 | gotPositive = text.regionMatches(position, positivePrefix, 0, | ||
| 1415 | positivePrefix.length()); | ||
| 1416 | gotNegative = text.regionMatches(position, negativePrefix, 0, | ||
| 1417 | negativePrefix.length()); | ||
| 1418 | |||
| 1419 | if (gotPositive && gotNegative) { | ||
| 1420 | if (positivePrefix.length() > negativePrefix.length()) { | ||
| 1421 | gotNegative = false; | ||
| 1422 | } else if (positivePrefix.length() < negativePrefix.length()) { | ||
| 1423 | gotPositive = false; | ||
| 1424 | } | ||
| 1425 | } | ||
| 1426 | |||
| 1427 | if (gotPositive) { | ||
| 1428 | position += positivePrefix.length(); | ||
| 1429 | } else if (gotNegative) { | ||
| 1430 | position += negativePrefix.length(); | ||
| 1431 | } else { | ||
| 1432 | parsePosition.errorIndex = position; | ||
| 1433 | return false; | ||
| 1434 | } | ||
| 1435 | |||
| 1436 | // process digits or Inf, find decimal position | ||
| 1437 | status[STATUS_INFINITE] = false; | ||
| 1438 | if (!isExponent && text.regionMatches(position,symbols.getInfinity(),0, | ||
| 1439 | symbols.getInfinity().length())) { | ||
| 1440 | position += symbols.getInfinity().length(); | ||
| 1441 | status[STATUS_INFINITE] = true; | ||
| 1442 | } else { | ||
| 1443 | // We now have a string of digits, possibly with grouping symbols, | ||
| 1444 | // and decimal points. We want to process these into a DigitList. | ||
| 1445 | // We don't want to put a bunch of leading zeros into the DigitList | ||
| 1446 | // though, so we keep track of the location of the decimal point, | ||
| 1447 | // put only significant digits into the DigitList, and adjust the | ||
| 1448 | // exponent as needed. | ||
| 1449 | |||
| 1450 | digits.decimalAt = digits.count = 0; | ||
| 1451 | char zero = symbols.getZeroDigit(); | ||
| 1452 | char decimal = isCurrencyFormat ? | ||
| 1453 | symbols.getMonetaryDecimalSeparator() : | ||
| 1454 | symbols.getDecimalSeparator(); | ||
| 1455 | char grouping = symbols.getGroupingSeparator(); | ||
| 1456 | String exponentString = symbols.getExponentSeparator(); | ||
| 1457 | boolean sawDecimal = false; | ||
| 1458 | boolean sawExponent = false; | ||
| 1459 | boolean sawDigit = false; | ||
| 1460 | int exponent = 0; // Set to the exponent value, if any | ||
| 1461 | |||
| 1462 | // We have to track digitCount ourselves, because digits.count will | ||
| 1463 | // pin when the maximum allowable digits is reached. | ||
| 1464 | int digitCount = 0; | ||
| 1465 | |||
| 1466 | backup = -1; | ||
| 1467 | for (; position < text.length(); ++position) { | ||
| 1468 | char ch = text.charAt(position); | ||
| 1469 | |||
| 1470 | /* We recognize all digit ranges, not only the Latin digit range | ||
| 1471 | * '0'..'9'. We do so by using the Character.digit() method, | ||
| 1472 | * which converts a valid Unicode digit to the range 0..9. | ||
| 1473 | * | ||
| 1474 | * The character 'ch' may be a digit. If so, place its value | ||
| 1475 | * from 0 to 9 in 'digit'. First try using the locale digit, | ||
| 1476 | * which may or MAY NOT be a standard Unicode digit range. If | ||
| 1477 | * this fails, try using the standard Unicode digit ranges by | ||
| 1478 | * calling Character.digit(). If this also fails, digit will | ||
| 1479 | * have a value outside the range 0..9. | ||
| 1480 | */ | ||
| 1481 | int digit = ch - zero; | ||
| 1482 | if (digit < 0 || digit > 9) { | ||
| 1483 | digit = Character.digit(ch, 10); | ||
| 1484 | } | ||
| 1485 | |||
| 1486 | if (digit == 0) { | ||
| 1487 | // Cancel out backup setting (see grouping handler below) | ||
| 1488 | backup = -1; // Do this BEFORE continue statement below!!! | ||
| 1489 | sawDigit = true; | ||
| 1490 | |||
| 1491 | // Handle leading zeros | ||
| 1492 | if (digits.count == 0) { | ||
| 1493 | // Ignore leading zeros in integer part of number. | ||
| 1494 | if (!sawDecimal) { | ||
| 1495 | continue; | ||
| 1496 | } | ||
| 1497 | |||
| 1498 | // If we have seen the decimal, but no significant | ||
| 1499 | // digits yet, then we account for leading zeros by | ||
| 1500 | // decrementing the digits.decimalAt into negative | ||
| 1501 | // values. | ||
| 1502 | --digits.decimalAt; | ||
| 1503 | } else { | ||
| 1504 | ++digitCount; | ||
| 1505 | digits.append((char)(digit + '0')); | ||
| 1506 | } | ||
| 1507 | } else if (digit > 0 && digit <= 9) { // [sic] digit==0 handled above | ||
| 1508 | sawDigit = true; | ||
| 1509 | ++digitCount; | ||
| 1510 | digits.append((char)(digit + '0')); | ||
| 1511 | |||
| 1512 | // Cancel out backup setting (see grouping handler below) | ||
| 1513 | backup = -1; | ||
| 1514 | } else if (!isExponent && ch == decimal) { | ||
| 1515 | // If we're only parsing integers, or if we ALREADY saw the | ||
| 1516 | // decimal, then don't parse this one. | ||
| 1517 | if (isParseIntegerOnly() || sawDecimal) { | ||
| 1518 | break; | ||
| 1519 | } | ||
| 1520 | digits.decimalAt = digitCount; // Not digits.count! | ||
| 1521 | sawDecimal = true; | ||
| 1522 | } else if (!isExponent && ch == grouping && isGroupingUsed()) { | ||
| 1523 | if (sawDecimal) { | ||
| 1524 | break; | ||
| 1525 | } | ||
| 1526 | // Ignore grouping characters, if we are using them, but | ||
| 1527 | // require that they be followed by a digit. Otherwise | ||
| 1528 | // we backup and reprocess them. | ||
| 1529 | backup = position; | ||
| 1530 | } else if (!isExponent && text.regionMatches(position, exponentString, 0, exponentString.length()) | ||
| 1531 | && !sawExponent) { | ||
| 1532 | // Process the exponent by recursively calling this method. | ||
| 1533 | ParsePosition pos = new ParsePosition(position + exponentString.length()); | ||
| 1534 | boolean[] stat = new boolean[STATUS_LENGTH]; | ||
| 1535 | DigitList exponentDigits = new DigitList(); | ||
| 1536 | |||
| 1537 | if (subparse(text, pos, "", Character.toString(symbols.getMinusSign()), exponentDigits, true, stat) && | ||
| 1538 | exponentDigits.fitsIntoLong(stat[STATUS_POSITIVE], true)) { | ||
| 1539 | position = pos.index; // Advance past the exponent | ||
| 1540 | exponent = (int)exponentDigits.getLong(); | ||
| 1541 | if (!stat[STATUS_POSITIVE]) { | ||
| 1542 | exponent = -exponent; | ||
| 1543 | } | ||
| 1544 | sawExponent = true; | ||
| 1545 | } | ||
| 1546 | break; // Whether we fail or succeed, we exit this loop | ||
| 1547 | } | ||
| 1548 | else { | ||
| 1549 | break; | ||
| 1550 | } | ||
| 1551 | } | ||
| 1552 | |||
| 1553 | if (backup != -1) { | ||
| 1554 | position = backup; | ||
| 1555 | } | ||
| 1556 | |||
| 1557 | // If there was no decimal point we have an integer | ||
| 1558 | if (!sawDecimal) { | ||
| 1559 | digits.decimalAt = digitCount; // Not digits.count! | ||
| 1560 | } | ||
| 1561 | |||
| 1562 | // Adjust for exponent, if any | ||
| 1563 | digits.decimalAt += exponent; | ||
| 1564 | |||
| 1565 | // If none of the text string was recognized. For example, parse | ||
| 1566 | // "x" with pattern "#0.00" (return index and error index both 0) | ||
| 1567 | // parse "$" with pattern "$#0.00". (return index 0 and error | ||
| 1568 | // index 1). | ||
| 1569 | if (!sawDigit && digitCount == 0) { | ||
| 1570 | parsePosition.index = oldStart; | ||
| 1571 | parsePosition.errorIndex = oldStart; | ||
| 1572 | return false; | ||
| 1573 | } | ||
| 1574 | } | ||
| 1575 | |||
| 1576 | // check for suffix | ||
| 1577 | if (!isExponent) { | ||
| 1578 | if (gotPositive) { | ||
| 1579 | gotPositive = text.regionMatches(position,positiveSuffix,0, | ||
| 1580 | positiveSuffix.length()); | ||
| 1581 | } | ||
| 1582 | if (gotNegative) { | ||
| 1583 | gotNegative = text.regionMatches(position,negativeSuffix,0, | ||
| 1584 | negativeSuffix.length()); | ||
| 1585 | } | ||
| 1586 | |||
| 1587 | // if both match, take longest | ||
| 1588 | if (gotPositive && gotNegative) { | ||
| 1589 | if (positiveSuffix.length() > negativeSuffix.length()) { | ||
| 1590 | gotNegative = false; | ||
| 1591 | } else if (positiveSuffix.length() < negativeSuffix.length()) { | ||
| 1592 | gotPositive = false; | ||
| 1593 | } | ||
| 1594 | } | ||
| 1595 | |||
| 1596 | // fail if neither or both | ||
| 1597 | if (gotPositive == gotNegative) { | ||
| 1598 | parsePosition.errorIndex = position; | ||
| 1599 | return false; | ||
| 1600 | } | ||
| 1601 | |||
| 1602 | parsePosition.index = position + | ||
| 1603 | (gotPositive ? positiveSuffix.length() : negativeSuffix.length()); // mark success! | ||
| 1604 | } else { | ||
| 1605 | parsePosition.index = position; | ||
| 1606 | } | ||
| 1607 | |||
| 1608 | status[STATUS_POSITIVE] = gotPositive; | ||
| 1609 | if (parsePosition.index == oldStart) { | ||
| 1610 | parsePosition.errorIndex = position; | ||
| 1611 | return false; | ||
| 1612 | } | ||
| 1613 | return true; | ||
| 1614 | } | ||
| 1615 | |||
| 1616 | /** | ||
| 1617 | * Returns a copy of the decimal format symbols, which is generally not | ||
| 1618 | * changed by the programmer or user. | ||
| 1619 | * @return a copy of the desired DecimalFormatSymbols | ||
| 1620 | * @see java.text.DecimalFormatSymbols | ||
| 1621 | */ | ||
| 1622 | public DecimalFormatSymbols getDecimalFormatSymbols() { | ||
| 1623 | try { | ||
| 1624 | // don't allow multiple references | ||
| 1625 | return (DecimalFormatSymbols) symbols.clone(); | ||
| 1626 | } catch (Exception foo) { | ||
| 1627 | return null; // should never happen | ||
| 1628 | } | ||
| 1629 | } | ||
| 1630 | |||
| 1631 | |||
| 1632 | /** | ||
| 1633 | * Sets the decimal format symbols, which is generally not changed | ||
| 1634 | * by the programmer or user. | ||
| 1635 | * @param newSymbols desired DecimalFormatSymbols | ||
| 1636 | * @see java.text.DecimalFormatSymbols | ||
| 1637 | */ | ||
| 1638 | public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) { | ||
| 1639 | try { | ||
| 1640 | // don't allow multiple references | ||
| 1641 | symbols = (DecimalFormatSymbols) newSymbols.clone(); | ||
| 1642 | expandAffixes(); | ||
| 1643 | } catch (Exception foo) { | ||
| 1644 | // should never happen | ||
| 1645 | } | ||
| 1646 | } | ||
| 1647 | |||
| 1648 | /** | ||
| 1649 | * Get the positive prefix. | ||
| 1650 | * <P>Examples: +123, $123, sFr123 | ||
| 1651 | */ | ||
| 1652 | public String getPositivePrefix () { | ||
| 1653 | return positivePrefix; | ||
| 1654 | } | ||
| 1655 | |||
| 1656 | /** | ||
| 1657 | * Set the positive prefix. | ||
| 1658 | * <P>Examples: +123, $123, sFr123 | ||
| 1659 | */ | ||
| 1660 | public void setPositivePrefix (String newValue) { | ||
| 1661 | positivePrefix = newValue; | ||
| 1662 | posPrefixPattern = null; | ||
| 1663 | positivePrefixFieldPositions = null; | ||
| 1664 | } | ||
| 1665 | |||
| 1666 | /** | ||
| 1667 | * Returns the FieldPositions of the fields in the prefix used for | ||
| 1668 | * positive numbers. This is not used if the user has explicitly set | ||
| 1669 | * a positive prefix via <code>setPositivePrefix</code>. This is | ||
| 1670 | * lazily created. | ||
| 1671 | * | ||
| 1672 | * @return FieldPositions in positive prefix | ||
| 1673 | */ | ||
| 1674 | private FieldPosition[] getPositivePrefixFieldPositions() { | ||
| 1675 | if (positivePrefixFieldPositions == null) { | ||
| 1676 | if (posPrefixPattern != null) { | ||
| 1677 | positivePrefixFieldPositions = expandAffix(posPrefixPattern); | ||
| 1678 | } | ||
| 1679 | else { | ||
| 1680 | positivePrefixFieldPositions = EmptyFieldPositionArray; | ||
| 1681 | } | ||
| 1682 | } | ||
| 1683 | return positivePrefixFieldPositions; | ||
| 1684 | } | ||
| 1685 | |||
| 1686 | /** | ||
| 1687 | * Get the negative prefix. | ||
| 1688 | * <P>Examples: -123, ($123) (with negative suffix), sFr-123 | ||
| 1689 | */ | ||
| 1690 | public String getNegativePrefix () { | ||
| 1691 | return negativePrefix; | ||
| 1692 | } | ||
| 1693 | |||
| 1694 | /** | ||
| 1695 | * Set the negative prefix. | ||
| 1696 | * <P>Examples: -123, ($123) (with negative suffix), sFr-123 | ||
| 1697 | */ | ||
| 1698 | public void setNegativePrefix (String newValue) { | ||
| 1699 | negativePrefix = newValue; | ||
| 1700 | negPrefixPattern = null; | ||
| 1701 | } | ||
| 1702 | |||
| 1703 | /** | ||
| 1704 | * Returns the FieldPositions of the fields in the prefix used for | ||
| 1705 | * negative numbers. This is not used if the user has explicitly set | ||
| 1706 | * a negative prefix via <code>setNegativePrefix</code>. This is | ||
| 1707 | * lazily created. | ||
| 1708 | * | ||
| 1709 | * @return FieldPositions in positive prefix | ||
| 1710 | */ | ||
| 1711 | private FieldPosition[] getNegativePrefixFieldPositions() { | ||
| 1712 | if (negativePrefixFieldPositions == null) { | ||
| 1713 | if (negPrefixPattern != null) { | ||
| 1714 | negativePrefixFieldPositions = expandAffix(negPrefixPattern); | ||
| 1715 | } | ||
| 1716 | else { | ||
| 1717 | negativePrefixFieldPositions = EmptyFieldPositionArray; | ||
| 1718 | } | ||
| 1719 | } | ||
| 1720 | return negativePrefixFieldPositions; | ||
| 1721 | } | ||
| 1722 | |||
| 1723 | /** | ||
| 1724 | * Get the positive suffix. | ||
| 1725 | * <P>Example: 123% | ||
| 1726 | */ | ||
| 1727 | public String getPositiveSuffix () { | ||
| 1728 | return positiveSuffix; | ||
| 1729 | } | ||
| 1730 | |||
| 1731 | /** | ||
| 1732 | * Set the positive suffix. | ||
| 1733 | * <P>Example: 123% | ||
| 1734 | */ | ||
| 1735 | public void setPositiveSuffix (String newValue) { | ||
| 1736 | positiveSuffix = newValue; | ||
| 1737 | posSuffixPattern = null; | ||
| 1738 | } | ||
| 1739 | |||
| 1740 | /** | ||
| 1741 | * Returns the FieldPositions of the fields in the suffix used for | ||
| 1742 | * positive numbers. This is not used if the user has explicitly set | ||
| 1743 | * a positive suffix via <code>setPositiveSuffix</code>. This is | ||
| 1744 | * lazily created. | ||
| 1745 | * | ||
| 1746 | * @return FieldPositions in positive prefix | ||
| 1747 | */ | ||
| 1748 | private FieldPosition[] getPositiveSuffixFieldPositions() { | ||
| 1749 | if (positiveSuffixFieldPositions == null) { | ||
| 1750 | if (posSuffixPattern != null) { | ||
| 1751 | positiveSuffixFieldPositions = expandAffix(posSuffixPattern); | ||
| 1752 | } | ||
| 1753 | else { | ||
| 1754 | positiveSuffixFieldPositions = EmptyFieldPositionArray; | ||
| 1755 | } | ||
| 1756 | } | ||
| 1757 | return positiveSuffixFieldPositions; | ||
| 1758 | } | ||
| 1759 | |||
| 1760 | /** | ||
| 1761 | * Get the negative suffix. | ||
| 1762 | * <P>Examples: -123%, ($123) (with positive suffixes) | ||
| 1763 | */ | ||
| 1764 | public String getNegativeSuffix () { | ||
| 1765 | return negativeSuffix; | ||
| 1766 | } | ||
| 1767 | |||
| 1768 | /** | ||
| 1769 | * Set the negative suffix. | ||
| 1770 | * <P>Examples: 123% | ||
| 1771 | */ | ||
| 1772 | public void setNegativeSuffix (String newValue) { | ||
| 1773 | negativeSuffix = newValue; | ||
| 1774 | negSuffixPattern = null; | ||
| 1775 | } | ||
| 1776 | |||
| 1777 | /** | ||
| 1778 | * Returns the FieldPositions of the fields in the suffix used for | ||
| 1779 | * negative numbers. This is not used if the user has explicitly set | ||
| 1780 | * a negative suffix via <code>setNegativeSuffix</code>. This is | ||
| 1781 | * lazily created. | ||
| 1782 | * | ||
| 1783 | * @return FieldPositions in positive prefix | ||
| 1784 | */ | ||
| 1785 | private FieldPosition[] getNegativeSuffixFieldPositions() { | ||
| 1786 | if (negativeSuffixFieldPositions == null) { | ||
| 1787 | if (negSuffixPattern != null) { | ||
| 1788 | negativeSuffixFieldPositions = expandAffix(negSuffixPattern); | ||
| 1789 | } | ||
| 1790 | else { | ||
| 1791 | negativeSuffixFieldPositions = EmptyFieldPositionArray; | ||
| 1792 | } | ||
| 1793 | } | ||
| 1794 | return negativeSuffixFieldPositions; | ||
| 1795 | } | ||
| 1796 | |||
| 1797 | /** | ||
| 1798 | * Gets the multiplier for use in percent, per mille, and similar | ||
| 1799 | * formats. | ||
| 1800 | * | ||
| 1801 | * @see #setMultiplier(int) | ||
| 1802 | */ | ||
| 1803 | public int getMultiplier () { | ||
| 1804 | return multiplier; | ||
| 1805 | } | ||
| 1806 | |||
| 1807 | /** | ||
| 1808 | * Sets the multiplier for use in percent, per mille, and similar | ||
| 1809 | * formats. | ||
| 1810 | * For a percent format, set the multiplier to 100 and the suffixes to | ||
| 1811 | * have '%' (for Arabic, use the Arabic percent sign). | ||
| 1812 | * For a per mille format, set the multiplier to 1000 and the suffixes to | ||
| 1813 | * have '\u2030'. | ||
| 1814 | * | ||
| 1815 | * <P>Example: with multiplier 100, 1.23 is formatted as "123", and | ||
| 1816 | * "123" is parsed into 1.23. | ||
| 1817 | * | ||
| 1818 | * @see #getMultiplier | ||
| 1819 | */ | ||
| 1820 | public void setMultiplier (int newValue) { | ||
| 1821 | multiplier = newValue; | ||
| 1822 | bigDecimalMultiplier = null; | ||
| 1823 | bigIntegerMultiplier = null; | ||
| 1824 | } | ||
| 1825 | |||
| 1826 | /** | ||
| 1827 | * Return the grouping size. Grouping size is the number of digits between | ||
| 1828 | * grouping separators in the integer portion of a number. For example, | ||
| 1829 | * in the number "123,456.78", the grouping size is 3. | ||
| 1830 | * @see #setGroupingSize | ||
| 1831 | * @see java.text.NumberFormat#isGroupingUsed | ||
| 1832 | * @see java.text.DecimalFormatSymbols#getGroupingSeparator | ||
| 1833 | */ | ||
| 1834 | public int getGroupingSize () { | ||
| 1835 | return groupingSize; | ||
| 1836 | } | ||
| 1837 | |||
| 1838 | /** | ||
| 1839 | * Set the grouping size. Grouping size is the number of digits between | ||
| 1840 | * grouping separators in the integer portion of a number. For example, | ||
| 1841 | * in the number "123,456.78", the grouping size is 3. | ||
| 1842 | * <br> | ||
| 1843 | * The value passed in is converted to a byte, which may lose information. | ||
| 1844 | * @see #getGroupingSize | ||
| 1845 | * @see java.text.NumberFormat#setGroupingUsed | ||
| 1846 | * @see java.text.DecimalFormatSymbols#setGroupingSeparator | ||
| 1847 | */ | ||
| 1848 | public void setGroupingSize (int newValue) { | ||
| 1849 | groupingSize = (byte)newValue; | ||
| 1850 | } | ||
| 1851 | |||
| 1852 | /** | ||
| 1853 | * Allows you to get the behavior of the decimal separator with integers. | ||
| 1854 | * (The decimal separator will always appear with decimals.) | ||
| 1855 | * <P>Example: Decimal ON: 12345 -> 12345.; OFF: 12345 -> 12345 | ||
| 1856 | */ | ||
| 1857 | public boolean isDecimalSeparatorAlwaysShown() { | ||
| 1858 | return decimalSeparatorAlwaysShown; | ||
| 1859 | } | ||
| 1860 | |||
| 1861 | /** | ||
| 1862 | * Allows you to set the behavior of the decimal separator with integers. | ||
| 1863 | * (The decimal separator will always appear with decimals.) | ||
| 1864 | * <P>Example: Decimal ON: 12345 -> 12345.; OFF: 12345 -> 12345 | ||
| 1865 | */ | ||
| 1866 | public void setDecimalSeparatorAlwaysShown(boolean newValue) { | ||
| 1867 | decimalSeparatorAlwaysShown = newValue; | ||
| 1868 | } | ||
| 1869 | |||
| 1870 | /** | ||
| 1871 | * Returns whether the {@link #parse(java.lang.String, java.text.ParsePosition)} | ||
| 1872 | * method returns <code>BigDecimal</code>. The default value is false. | ||
| 1873 | * @see #setParseBigDecimal | ||
| 1874 | * @since 1.5 | ||
| 1875 | */ | ||
| 1876 | public boolean isParseBigDecimal() { | ||
| 1877 | return parseBigDecimal; | ||
| 1878 | } | ||
| 1879 | |||
| 1880 | /** | ||
| 1881 | * Sets whether the {@link #parse(java.lang.String, java.text.ParsePosition)} | ||
| 1882 | * method returns <code>BigDecimal</code>. | ||
| 1883 | * @see #isParseBigDecimal | ||
| 1884 | * @since 1.5 | ||
| 1885 | */ | ||
| 1886 | public void setParseBigDecimal(boolean newValue) { | ||
| 1887 | parseBigDecimal = newValue; | ||
| 1888 | } | ||
| 1889 | |||
| 1890 | /** | ||
| 1891 | * Standard override; no change in semantics. | ||
| 1892 | */ | ||
| 1893 | public Object clone() { | ||
| 1894 | try { | ||
| 1895 | DecimalFormat other = (DecimalFormat) super.clone(); | ||
| 1896 | other.symbols = (DecimalFormatSymbols) symbols.clone(); | ||
| 1897 | other.digitList = (DigitList) digitList.clone(); | ||
| 1898 | return other; | ||
| 1899 | } catch (Exception e) { | ||
| 1900 | throw new InternalError(); | ||
| 1901 | } | ||
| 1902 | } | ||
| 1903 | |||
| 1904 | /** | ||
| 1905 | * Overrides equals | ||
| 1906 | */ | ||
| 1907 | public boolean equals(Object obj) | ||
| 1908 | { | ||
| 1909 | if (obj == null) return false; | ||
| 1910 | if (!super.equals(obj)) return false; // super does class check | ||
| 1911 | DecimalFormat other = (DecimalFormat) obj; | ||
| 1912 | return ((posPrefixPattern == other.posPrefixPattern && | ||
| 1913 | positivePrefix.equals(other.positivePrefix)) | ||
| 1914 | || (posPrefixPattern != null && | ||
| 1915 | posPrefixPattern.equals(other.posPrefixPattern))) | ||
| 1916 | && ((posSuffixPattern == other.posSuffixPattern && | ||
| 1917 | positiveSuffix.equals(other.positiveSuffix)) | ||
| 1918 | || (posSuffixPattern != null && | ||
| 1919 | posSuffixPattern.equals(other.posSuffixPattern))) | ||
| 1920 | && ((negPrefixPattern == other.negPrefixPattern && | ||
| 1921 | negativePrefix.equals(other.negativePrefix)) | ||
| 1922 | || (negPrefixPattern != null && | ||
| 1923 | negPrefixPattern.equals(other.negPrefixPattern))) | ||
| 1924 | && ((negSuffixPattern == other.negSuffixPattern && | ||
| 1925 | negativeSuffix.equals(other.negativeSuffix)) | ||
| 1926 | || (negSuffixPattern != null && | ||
| 1927 | negSuffixPattern.equals(other.negSuffixPattern))) | ||
| 1928 | && multiplier == other.multiplier | ||
| 1929 | && groupingSize == other.groupingSize | ||
| 1930 | && decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown | ||
| 1931 | && parseBigDecimal == other.parseBigDecimal | ||
| 1932 | && useExponentialNotation == other.useExponentialNotation | ||
| 1933 | && (!useExponentialNotation || | ||
| 1934 | minExponentDigits == other.minExponentDigits) | ||
| 1935 | && maximumIntegerDigits == other.maximumIntegerDigits | ||
| 1936 | && minimumIntegerDigits == other.minimumIntegerDigits | ||
| 1937 | && maximumFractionDigits == other.maximumFractionDigits | ||
| 1938 | && minimumFractionDigits == other.minimumFractionDigits | ||
| 1939 | && roundingMode == other.roundingMode | ||
| 1940 | && symbols.equals(other.symbols); | ||
| 1941 | } | ||
| 1942 | |||
| 1943 | /** | ||
| 1944 | * Overrides hashCode | ||
| 1945 | */ | ||
| 1946 | public int hashCode() { | ||
| 1947 | return super.hashCode() * 37 + positivePrefix.hashCode(); | ||
| 1948 | // just enough fields for a reasonable distribution | ||
| 1949 | } | ||
| 1950 | |||
| 1951 | /** | ||
| 1952 | * Synthesizes a pattern string that represents the current state | ||
| 1953 | * of this Format object. | ||
| 1954 | * @see #applyPattern | ||
| 1955 | */ | ||
| 1956 | public String toPattern() { | ||
| 1957 | return toPattern( false ); | ||
| 1958 | } | ||
| 1959 | |||
| 1960 | /** | ||
| 1961 | * Synthesizes a localized pattern string that represents the current | ||
| 1962 | * state of this Format object. | ||
| 1963 | * @see #applyPattern | ||
| 1964 | */ | ||
| 1965 | public String toLocalizedPattern() { | ||
| 1966 | return toPattern( true ); | ||
| 1967 | } | ||
| 1968 | |||
| 1969 | /** | ||
| 1970 | * Expand the affix pattern strings into the expanded affix strings. If any | ||
| 1971 | * affix pattern string is null, do not expand it. This method should be | ||
| 1972 | * called any time the symbols or the affix patterns change in order to keep | ||
| 1973 | * the expanded affix strings up to date. | ||
| 1974 | */ | ||
| 1975 | private void expandAffixes() { | ||
| 1976 | // Reuse one StringBuffer for better performance | ||
| 1977 | StringBuffer buffer = new StringBuffer(); | ||
| 1978 | if (posPrefixPattern != null) { | ||
| 1979 | positivePrefix = expandAffix(posPrefixPattern, buffer); | ||
| 1980 | positivePrefixFieldPositions = null; | ||
| 1981 | } | ||
| 1982 | if (posSuffixPattern != null) { | ||
| 1983 | positiveSuffix = expandAffix(posSuffixPattern, buffer); | ||
| 1984 | positiveSuffixFieldPositions = null; | ||
| 1985 | } | ||
| 1986 | if (negPrefixPattern != null) { | ||
| 1987 | negativePrefix = expandAffix(negPrefixPattern, buffer); | ||
| 1988 | negativePrefixFieldPositions = null; | ||
| 1989 | } | ||
| 1990 | if (negSuffixPattern != null) { | ||
| 1991 | negativeSuffix = expandAffix(negSuffixPattern, buffer); | ||
| 1992 | negativeSuffixFieldPositions = null; | ||
| 1993 | } | ||
| 1994 | } | ||
| 1995 | |||
| 1996 | /** | ||
| 1997 | * Expand an affix pattern into an affix string. All characters in the | ||
| 1998 | * pattern are literal unless prefixed by QUOTE. The following characters | ||
| 1999 | * after QUOTE are recognized: PATTERN_PERCENT, PATTERN_PER_MILLE, | ||
| 2000 | * PATTERN_MINUS, and CURRENCY_SIGN. If CURRENCY_SIGN is doubled (QUOTE + | ||
| 2001 | * CURRENCY_SIGN + CURRENCY_SIGN), it is interpreted as an ISO 4217 | ||
| 2002 | * currency code. Any other character after a QUOTE represents itself. | ||
| 2003 | * QUOTE must be followed by another character; QUOTE may not occur by | ||
| 2004 | * itself at the end of the pattern. | ||
| 2005 | * | ||
| 2006 | * @param pattern the non-null, possibly empty pattern | ||
| 2007 | * @param buffer a scratch StringBuffer; its contents will be lost | ||
| 2008 | * @return the expanded equivalent of pattern | ||
| 2009 | */ | ||
| 2010 | private String expandAffix(String pattern, StringBuffer buffer) { | ||
| 2011 | buffer.setLength(0); | ||
| 2012 | for (int i=0; i<pattern.length(); ) { | ||
| 2013 | char c = pattern.charAt(i++); | ||
| 2014 | if (c == QUOTE) { | ||
| 2015 | c = pattern.charAt(i++); | ||
| 2016 | switch (c) { | ||
| 2017 | case CURRENCY_SIGN: | ||
| 2018 | if (i<pattern.length() && | ||
| 2019 | pattern.charAt(i) == CURRENCY_SIGN) { | ||
| 2020 | ++i; | ||
| 2021 | buffer.append(symbols.getInternationalCurrencySymbol()); | ||
| 2022 | } else { | ||
| 2023 | buffer.append(symbols.getCurrencySymbol()); | ||
| 2024 | } | ||
| 2025 | continue; | ||
| 2026 | case PATTERN_PERCENT: | ||
| 2027 | c = symbols.getPercent(); | ||
| 2028 | break; | ||
| 2029 | case PATTERN_PER_MILLE: | ||
| 2030 | c = symbols.getPerMill(); | ||
| 2031 | break; | ||
| 2032 | case PATTERN_MINUS: | ||
| 2033 | c = symbols.getMinusSign(); | ||
| 2034 | break; | ||
| 2035 | } | ||
| 2036 | } | ||
| 2037 | buffer.append(c); | ||
| 2038 | } | ||
| 2039 | return buffer.toString(); | ||
| 2040 | } | ||
| 2041 | |||
| 2042 | /** | ||
| 2043 | * Expand an affix pattern into an array of FieldPositions describing | ||
| 2044 | * how the pattern would be expanded. | ||
| 2045 | * All characters in the | ||
| 2046 | * pattern are literal unless prefixed by QUOTE. The following characters | ||
| 2047 | * after QUOTE are recognized: PATTERN_PERCENT, PATTERN_PER_MILLE, | ||
| 2048 | * PATTERN_MINUS, and CURRENCY_SIGN. If CURRENCY_SIGN is doubled (QUOTE + | ||
| 2049 | * CURRENCY_SIGN + CURRENCY_SIGN), it is interpreted as an ISO 4217 | ||
| 2050 | * currency code. Any other character after a QUOTE represents itself. | ||
| 2051 | * QUOTE must be followed by another character; QUOTE may not occur by | ||
| 2052 | * itself at the end of the pattern. | ||
| 2053 | * | ||
| 2054 | * @param pattern the non-null, possibly empty pattern | ||
| 2055 | * @return FieldPosition array of the resulting fields. | ||
| 2056 | */ | ||
| 2057 | private FieldPosition[] expandAffix(String pattern) { | ||
| 2058 | ArrayList positions = null; | ||
| 2059 | int stringIndex = 0; | ||
| 2060 | for (int i=0; i<pattern.length(); ) { | ||
| 2061 | char c = pattern.charAt(i++); | ||
| 2062 | if (c == QUOTE) { | ||
| 2063 | int field = -1; | ||
| 2064 | Format.Field fieldID = null; | ||
| 2065 | c = pattern.charAt(i++); | ||
| 2066 | switch (c) { | ||
| 2067 | case CURRENCY_SIGN: | ||
| 2068 | String string; | ||
| 2069 | if (i<pattern.length() && | ||
| 2070 | pattern.charAt(i) == CURRENCY_SIGN) { | ||
| 2071 | ++i; | ||
| 2072 | string = symbols.getInternationalCurrencySymbol(); | ||
| 2073 | } else { | ||
| 2074 | string = symbols.getCurrencySymbol(); | ||
| 2075 | } | ||
| 2076 | if (string.length() > 0) { | ||
| 2077 | if (positions == null) { | ||
| 2078 | positions = new ArrayList(2); | ||
| 2079 | } | ||
| 2080 | FieldPosition fp = new FieldPosition(Field.CURRENCY); | ||
| 2081 | fp.setBeginIndex(stringIndex); | ||
| 2082 | fp.setEndIndex(stringIndex + string.length()); | ||
| 2083 | positions.add(fp); | ||
| 2084 | stringIndex += string.length(); | ||
| 2085 | } | ||
| 2086 | continue; | ||
| 2087 | case PATTERN_PERCENT: | ||
| 2088 | c = symbols.getPercent(); | ||
| 2089 | field = -1; | ||
| 2090 | fieldID = Field.PERCENT; | ||
| 2091 | break; | ||
| 2092 | case PATTERN_PER_MILLE: | ||
| 2093 | c = symbols.getPerMill(); | ||
| 2094 | field = -1; | ||
| 2095 | fieldID = Field.PERMILLE; | ||
| 2096 | break; | ||
| 2097 | case PATTERN_MINUS: | ||
| 2098 | c = symbols.getMinusSign(); | ||
| 2099 | field = -1; | ||
| 2100 | fieldID = Field.SIGN; | ||
| 2101 | break; | ||
| 2102 | } | ||
| 2103 | if (fieldID != null) { | ||
| 2104 | if (positions == null) { | ||
| 2105 | positions = new ArrayList(2); | ||
| 2106 | } | ||
| 2107 | FieldPosition fp = new FieldPosition(fieldID, field); | ||
| 2108 | fp.setBeginIndex(stringIndex); | ||
| 2109 | fp.setEndIndex(stringIndex + 1); | ||
| 2110 | positions.add(fp); | ||
| 2111 | } | ||
| 2112 | } | ||
| 2113 | stringIndex++; | ||
| 2114 | } | ||
| 2115 | if (positions != null) { | ||
| 2116 | return (FieldPosition[])positions.toArray(EmptyFieldPositionArray); | ||
| 2117 | } | ||
| 2118 | return EmptyFieldPositionArray; | ||
| 2119 | } | ||
| 2120 | |||
| 2121 | /** | ||
| 2122 | * Appends an affix pattern to the given StringBuffer, quoting special | ||
| 2123 | * characters as needed. Uses the internal affix pattern, if that exists, | ||
| 2124 | * or the literal affix, if the internal affix pattern is null. The | ||
| 2125 | * appended string will generate the same affix pattern (or literal affix) | ||
| 2126 | * when passed to toPattern(). | ||
| 2127 | * | ||
| 2128 | * @param buffer the affix string is appended to this | ||
| 2129 | * @param affixPattern a pattern such as posPrefixPattern; may be null | ||
| 2130 | * @param expAffix a corresponding expanded affix, such as positivePrefix. | ||
| 2131 | * Ignored unless affixPattern is null. If affixPattern is null, then | ||
| 2132 | * expAffix is appended as a literal affix. | ||
| 2133 | * @param localized true if the appended pattern should contain localized | ||
| 2134 | * pattern characters; otherwise, non-localized pattern chars are appended | ||
| 2135 | */ | ||
| 2136 | private void appendAffix(StringBuffer buffer, String affixPattern, | ||
| 2137 | String expAffix, boolean localized) { | ||
| 2138 | if (affixPattern == null) { | ||
| 2139 | appendAffix(buffer, expAffix, localized); | ||
| 2140 | } else { | ||
| 2141 | int i; | ||
| 2142 | for (int pos=0; pos<affixPattern.length(); pos=i) { | ||
| 2143 | i = affixPattern.indexOf(QUOTE, pos); | ||
| 2144 | if (i < 0) { | ||
| 2145 | appendAffix(buffer, affixPattern.substring(pos), localized); | ||
| 2146 | break; | ||
| 2147 | } | ||
| 2148 | if (i > pos) { | ||
| 2149 | appendAffix(buffer, affixPattern.substring(pos, i), localized); | ||
| 2150 | } | ||
| 2151 | char c = affixPattern.charAt(++i); | ||
| 2152 | ++i; | ||
| 2153 | if (c == QUOTE) { | ||
| 2154 | buffer.append(c); | ||
| 2155 | // Fall through and append another QUOTE below | ||
| 2156 | } else if (c == CURRENCY_SIGN && | ||
| 2157 | i<affixPattern.length() && | ||
| 2158 | affixPattern.charAt(i) == CURRENCY_SIGN) { | ||
| 2159 | ++i; | ||
| 2160 | buffer.append(c); | ||
| 2161 | // Fall through and append another CURRENCY_SIGN below | ||
| 2162 | } else if (localized) { | ||
| 2163 | switch (c) { | ||
| 2164 | case PATTERN_PERCENT: | ||
| 2165 | c = symbols.getPercent(); | ||
| 2166 | break; | ||
| 2167 | case PATTERN_PER_MILLE: | ||
| 2168 | c = symbols.getPerMill(); | ||
| 2169 | break; | ||
| 2170 | case PATTERN_MINUS: | ||
| 2171 | c = symbols.getMinusSign(); | ||
| 2172 | break; | ||
| 2173 | } | ||
| 2174 | } | ||
| 2175 | buffer.append(c); | ||
| 2176 | } | ||
| 2177 | } | ||
| 2178 | } | ||
| 2179 | |||
| 2180 | /** | ||
| 2181 | * Append an affix to the given StringBuffer, using quotes if | ||
| 2182 | * there are special characters. Single quotes themselves must be | ||
| 2183 | * escaped in either case. | ||
| 2184 | */ | ||
| 2185 | private void appendAffix(StringBuffer buffer, String affix, boolean localized) { | ||
| 2186 | boolean needQuote; | ||
| 2187 | if (localized) { | ||
| 2188 | needQuote = affix.indexOf(symbols.getZeroDigit()) >= 0 | ||
| 2189 | || affix.indexOf(symbols.getGroupingSeparator()) >= 0 | ||
| 2190 | || affix.indexOf(symbols.getDecimalSeparator()) >= 0 | ||
| 2191 | || affix.indexOf(symbols.getPercent()) >= 0 | ||
| 2192 | || affix.indexOf(symbols.getPerMill()) >= 0 | ||
| 2193 | || affix.indexOf(symbols.getDigit()) >= 0 | ||
| 2194 | || affix.indexOf(symbols.getPatternSeparator()) >= 0 | ||
| 2195 | || affix.indexOf(symbols.getMinusSign()) >= 0 | ||
| 2196 | || affix.indexOf(CURRENCY_SIGN) >= 0; | ||
| 2197 | } | ||
| 2198 | else { | ||
| 2199 | needQuote = affix.indexOf(PATTERN_ZERO_DIGIT) >= 0 | ||
| 2200 | || affix.indexOf(PATTERN_GROUPING_SEPARATOR) >= 0 | ||
| 2201 | || affix.indexOf(PATTERN_DECIMAL_SEPARATOR) >= 0 | ||
| 2202 | || affix.indexOf(PATTERN_PERCENT) >= 0 | ||
| 2203 | || affix.indexOf(PATTERN_PER_MILLE) >= 0 | ||
| 2204 | || affix.indexOf(PATTERN_DIGIT) >= 0 | ||
| 2205 | || affix.indexOf(PATTERN_SEPARATOR) >= 0 | ||
| 2206 | || affix.indexOf(PATTERN_MINUS) >= 0 | ||
| 2207 | || affix.indexOf(CURRENCY_SIGN) >= 0; | ||
| 2208 | } | ||
| 2209 | if (needQuote) buffer.append('\''); | ||
| 2210 | if (affix.indexOf('\'') < 0) buffer.append(affix); | ||
| 2211 | else { | ||
| 2212 | for (int j=0; j<affix.length(); ++j) { | ||
| 2213 | char c = affix.charAt(j); | ||
| 2214 | buffer.append(c); | ||
| 2215 | if (c == '\'') buffer.append(c); | ||
| 2216 | } | ||
| 2217 | } | ||
| 2218 | if (needQuote) buffer.append('\''); | ||
| 2219 | } | ||
| 2220 | |||
| 2221 | /** | ||
| 2222 | * Does the real work of generating a pattern. */ | ||
| 2223 | private String toPattern(boolean localized) { | ||
| 2224 | StringBuffer result = new StringBuffer(); | ||
| 2225 | for (int j = 1; j >= 0; --j) { | ||
| 2226 | if (j == 1) | ||
| 2227 | appendAffix(result, posPrefixPattern, positivePrefix, localized); | ||
| 2228 | else appendAffix(result, negPrefixPattern, negativePrefix, localized); | ||
| 2229 | int i; | ||
| 2230 | int digitCount = useExponentialNotation | ||
| 2231 | ? getMaximumIntegerDigits() | ||
| 2232 | : Math.max(groupingSize, getMinimumIntegerDigits())+1; | ||
| 2233 | for (i = digitCount; i > 0; --i) { | ||
| 2234 | if (i != digitCount && isGroupingUsed() && groupingSize != 0 && | ||
| 2235 | i % groupingSize == 0) { | ||
| 2236 | result.append(localized ? symbols.getGroupingSeparator() : | ||
| 2237 | PATTERN_GROUPING_SEPARATOR); | ||
| 2238 | } | ||
| 2239 | result.append(i <= getMinimumIntegerDigits() | ||
| 2240 | ? (localized ? symbols.getZeroDigit() : PATTERN_ZERO_DIGIT) | ||
| 2241 | : (localized ? symbols.getDigit() : PATTERN_DIGIT)); | ||
| 2242 | } | ||
| 2243 | if (getMaximumFractionDigits() > 0 || decimalSeparatorAlwaysShown) | ||
| 2244 | result.append(localized ? symbols.getDecimalSeparator() : | ||
| 2245 | PATTERN_DECIMAL_SEPARATOR); | ||
| 2246 | for (i = 0; i < getMaximumFractionDigits(); ++i) { | ||
| 2247 | if (i < getMinimumFractionDigits()) { | ||
| 2248 | result.append(localized ? symbols.getZeroDigit() : | ||
| 2249 | PATTERN_ZERO_DIGIT); | ||
| 2250 | } else { | ||
| 2251 | result.append(localized ? symbols.getDigit() : | ||
| 2252 | PATTERN_DIGIT); | ||
| 2253 | } | ||
| 2254 | } | ||
| 2255 | if (useExponentialNotation) | ||
| 2256 | { | ||
| 2257 | result.append(localized ? symbols.getExponentSeparator() : | ||
| 2258 | PATTERN_EXPONENT); | ||
| 2259 | for (i=0; i<minExponentDigits; ++i) | ||
| 2260 | result.append(localized ? symbols.getZeroDigit() : | ||
| 2261 | PATTERN_ZERO_DIGIT); | ||
| 2262 | } | ||
| 2263 | if (j == 1) { | ||
| 2264 | appendAffix(result, posSuffixPattern, positiveSuffix, localized); | ||
| 2265 | if ((negSuffixPattern == posSuffixPattern && // n == p == null | ||
| 2266 | negativeSuffix.equals(positiveSuffix)) | ||
| 2267 | || (negSuffixPattern != null && | ||
| 2268 | negSuffixPattern.equals(posSuffixPattern))) { | ||
| 2269 | if ((negPrefixPattern != null && posPrefixPattern != null && | ||
| 2270 | negPrefixPattern.equals("'-" + posPrefixPattern)) || | ||
| 2271 | (negPrefixPattern == posPrefixPattern && // n == p == null | ||
| 2272 | negativePrefix.equals(symbols.getMinusSign() + positivePrefix))) | ||
| 2273 | break; | ||
| 2274 | } | ||
| 2275 | result.append(localized ? symbols.getPatternSeparator() : | ||
| 2276 | PATTERN_SEPARATOR); | ||
| 2277 | } else appendAffix(result, negSuffixPattern, negativeSuffix, localized); | ||
| 2278 | } | ||
| 2279 | return result.toString(); | ||
| 2280 | } | ||
| 2281 | |||
| 2282 | /** | ||
| 2283 | * Apply the given pattern to this Format object. A pattern is a | ||
| 2284 | * short-hand specification for the various formatting properties. | ||
| 2285 | * These properties can also be changed individually through the | ||
| 2286 | * various setter methods. | ||
| 2287 | * <p> | ||
| 2288 | * There is no limit to integer digits set | ||
| 2289 | * by this routine, since that is the typical end-user desire; | ||
| 2290 | * use setMaximumInteger if you want to set a real value. | ||
| 2291 | * For negative numbers, use a second pattern, separated by a semicolon | ||
| 2292 | * <P>Example <code>"#,#00.0#"</code> -> 1,234.56 | ||
| 2293 | * <P>This means a minimum of 2 integer digits, 1 fraction digit, and | ||
| 2294 | * a maximum of 2 fraction digits. | ||
| 2295 | * <p>Example: <code>"#,#00.0#;(#,#00.0#)"</code> for negatives in | ||
| 2296 | * parentheses. | ||
| 2297 | * <p>In negative patterns, the minimum and maximum counts are ignored; | ||
| 2298 | * these are presumed to be set in the positive pattern. | ||
| 2299 | * | ||
| 2300 | * @exception NullPointerException if <code>pattern</code> is null | ||
| 2301 | * @exception IllegalArgumentException if the given pattern is invalid. | ||
| 2302 | */ | ||
| 2303 | public void applyPattern(String pattern) { | ||
| 2304 | applyPattern(pattern, false); | ||
| 2305 | } | ||
| 2306 | |||
| 2307 | /** | ||
| 2308 | * Apply the given pattern to this Format object. The pattern | ||
| 2309 | * is assumed to be in a localized notation. A pattern is a | ||
| 2310 | * short-hand specification for the various formatting properties. | ||
| 2311 | * These properties can also be changed individually through the | ||
| 2312 | * various setter methods. | ||
| 2313 | * <p> | ||
| 2314 | * There is no limit to integer digits set | ||
| 2315 | * by this routine, since that is the typical end-user desire; | ||
| 2316 | * use setMaximumInteger if you want to set a real value. | ||
| 2317 | * For negative numbers, use a second pattern, separated by a semicolon | ||
| 2318 | * <P>Example <code>"#,#00.0#"</code> -> 1,234.56 | ||
| 2319 | * <P>This means a minimum of 2 integer digits, 1 fraction digit, and | ||
| 2320 | * a maximum of 2 fraction digits. | ||
| 2321 | * <p>Example: <code>"#,#00.0#;(#,#00.0#)"</code> for negatives in | ||
| 2322 | * parentheses. | ||
| 2323 | * <p>In negative patterns, the minimum and maximum counts are ignored; | ||
| 2324 | * these are presumed to be set in the positive pattern. | ||
| 2325 | * | ||
| 2326 | * @exception NullPointerException if <code>pattern</code> is null | ||
| 2327 | * @exception IllegalArgumentException if the given pattern is invalid. | ||
| 2328 | */ | ||
| 2329 | public void applyLocalizedPattern(String pattern) { | ||
| 2330 | applyPattern(pattern, true); | ||
| 2331 | } | ||
| 2332 | |||
| 2333 | /** | ||
| 2334 | * Does the real work of applying a pattern. | ||
| 2335 | */ | ||
| 2336 | private void applyPattern(String pattern, boolean localized) { | ||
| 2337 | char zeroDigit = PATTERN_ZERO_DIGIT; | ||
| 2338 | char groupingSeparator = PATTERN_GROUPING_SEPARATOR; | ||
| 2339 | char decimalSeparator = PATTERN_DECIMAL_SEPARATOR; | ||
| 2340 | char percent = PATTERN_PERCENT; | ||
| 2341 | char perMill = PATTERN_PER_MILLE; | ||
| 2342 | char digit = PATTERN_DIGIT; | ||
| 2343 | char separator = PATTERN_SEPARATOR; | ||
| 2344 | String exponent = PATTERN_EXPONENT; | ||
| 2345 | char minus = PATTERN_MINUS; | ||
| 2346 | if (localized) { | ||
| 2347 | zeroDigit = symbols.getZeroDigit(); | ||
| 2348 | groupingSeparator = symbols.getGroupingSeparator(); | ||
| 2349 | decimalSeparator = symbols.getDecimalSeparator(); | ||
| 2350 | percent = symbols.getPercent(); | ||
| 2351 | perMill = symbols.getPerMill(); | ||
| 2352 | digit = symbols.getDigit(); | ||
| 2353 | separator = symbols.getPatternSeparator(); | ||
| 2354 | exponent = symbols.getExponentSeparator(); | ||
| 2355 | minus = symbols.getMinusSign(); | ||
| 2356 | } | ||
| 2357 | boolean gotNegative = false; | ||
| 2358 | decimalSeparatorAlwaysShown = false; | ||
| 2359 | isCurrencyFormat = false; | ||
| 2360 | useExponentialNotation = false; | ||
| 2361 | |||
| 2362 | // Two variables are used to record the subrange of the pattern | ||
| 2363 | // occupied by phase 1. This is used during the processing of the | ||
| 2364 | // second pattern (the one representing negative numbers) to ensure | ||
| 2365 | // that no deviation exists in phase 1 between the two patterns. | ||
| 2366 | int phaseOneStart = 0; | ||
| 2367 | int phaseOneLength = 0; | ||
| 2368 | |||
| 2369 | int start = 0; | ||
| 2370 | for (int j = 1; j >= 0 && start < pattern.length(); --j) { | ||
| 2371 | boolean inQuote = false; | ||
| 2372 | StringBuffer prefix = new StringBuffer(); | ||
| 2373 | StringBuffer suffix = new StringBuffer(); | ||
| 2374 | int decimalPos = -1; | ||
| 2375 | int multiplier = 1; | ||
| 2376 | int digitLeftCount = 0, zeroDigitCount = 0, digitRightCount = 0; | ||
| 2377 | byte groupingCount = -1; | ||
| 2378 | |||
| 2379 | // The phase ranges from 0 to 2. Phase 0 is the prefix. Phase 1 is | ||
| 2380 | // the section of the pattern with digits, decimal separator, | ||
| 2381 | // grouping characters. Phase 2 is the suffix. In phases 0 and 2, | ||
| 2382 | // percent, per mille, and currency symbols are recognized and | ||
| 2383 | // translated. The separation of the characters into phases is | ||
| 2384 | // strictly enforced; if phase 1 characters are to appear in the | ||
| 2385 | // suffix, for example, they must be quoted. | ||
| 2386 | int phase = 0; | ||
| 2387 | |||
| 2388 | // The affix is either the prefix or the suffix. | ||
| 2389 | StringBuffer affix = prefix; | ||
| 2390 | |||
| 2391 | for (int pos = start; pos < pattern.length(); ++pos) { | ||
| 2392 | char ch = pattern.charAt(pos); | ||
| 2393 | switch (phase) { | ||
| 2394 | case 0: | ||
| 2395 | case 2: | ||
| 2396 | // Process the prefix / suffix characters | ||
| 2397 | if (inQuote) { | ||
| 2398 | // A quote within quotes indicates either the closing | ||
| 2399 | // quote or two quotes, which is a quote literal. That | ||
| 2400 | // is, we have the second quote in 'do' or 'don''t'. | ||
| 2401 | if (ch == QUOTE) { | ||
| 2402 | if ((pos+1) < pattern.length() && | ||
| 2403 | pattern.charAt(pos+1) == QUOTE) { | ||
| 2404 | ++pos; | ||
| 2405 | affix.append("''"); // 'don''t' | ||
| 2406 | } else { | ||
| 2407 | inQuote = false; // 'do' | ||
| 2408 | } | ||
| 2409 | continue; | ||
| 2410 | } | ||
| 2411 | } else { | ||
| 2412 | // Process unquoted characters seen in prefix or suffix | ||
| 2413 | // phase. | ||
| 2414 | if (ch == digit || | ||
| 2415 | ch == zeroDigit || | ||
| 2416 | ch == groupingSeparator || | ||
| 2417 | ch == decimalSeparator) { | ||
| 2418 | phase = 1; | ||
| 2419 | if (j == 1) { | ||
| 2420 | phaseOneStart = pos; | ||
| 2421 | } | ||
| 2422 | --pos; // Reprocess this character | ||
| 2423 | continue; | ||
| 2424 | } else if (ch == CURRENCY_SIGN) { | ||
| 2425 | // Use lookahead to determine if the currency sign | ||
| 2426 | // is doubled or not. | ||
| 2427 | boolean doubled = (pos + 1) < pattern.length() && | ||
| 2428 | pattern.charAt(pos + 1) == CURRENCY_SIGN; | ||
| 2429 | if (doubled) { // Skip over the doubled character | ||
| 2430 | ++pos; | ||
| 2431 | } | ||
| 2432 | isCurrencyFormat = true; | ||
| 2433 | affix.append(doubled ? "'\u00A4\u00A4" : "'\u00A4"); | ||
| 2434 | continue; | ||
| 2435 | } else if (ch == QUOTE) { | ||
| 2436 | // A quote outside quotes indicates either the | ||
| 2437 | // opening quote or two quotes, which is a quote | ||
| 2438 | // literal. That is, we have the first quote in 'do' | ||
| 2439 | // or o''clock. | ||
| 2440 | if (ch == QUOTE) { | ||
| 2441 | if ((pos+1) < pattern.length() && | ||
| 2442 | pattern.charAt(pos+1) == QUOTE) { | ||
| 2443 | ++pos; | ||
| 2444 | affix.append("''"); // o''clock | ||
| 2445 | } else { | ||
| 2446 | inQuote = true; // 'do' | ||
| 2447 | } | ||
| 2448 | continue; | ||
| 2449 | } | ||
| 2450 | } else if (ch == separator) { | ||
| 2451 | // Don't allow separators before we see digit | ||
| 2452 | // characters of phase 1, and don't allow separators | ||
| 2453 | // in the second pattern (j == 0). | ||
| 2454 | if (phase == 0 || j == 0) { | ||
| 2455 | throw new IllegalArgumentException("Unquoted special character '" + | ||
| 2456 | ch + "' in pattern \"" + pattern + '"'); | ||
| 2457 | } | ||
| 2458 | start = pos + 1; | ||
| 2459 | pos = pattern.length(); | ||
| 2460 | continue; | ||
| 2461 | } | ||
| 2462 | |||
| 2463 | // Next handle characters which are appended directly. | ||
| 2464 | else if (ch == percent) { | ||
| 2465 | if (multiplier != 1) { | ||
| 2466 | throw new IllegalArgumentException("Too many percent/per mille characters in pattern \"" + | ||
| 2467 | pattern + '"'); | ||
| 2468 | } | ||
| 2469 | multiplier = 100; | ||
| 2470 | affix.append("'%"); | ||
| 2471 | continue; | ||
| 2472 | } else if (ch == perMill) { | ||
| 2473 | if (multiplier != 1) { | ||
| 2474 | throw new IllegalArgumentException("Too many percent/per mille characters in pattern \"" + | ||
| 2475 | pattern + '"'); | ||
| 2476 | } | ||
| 2477 | multiplier = 1000; | ||
| 2478 | affix.append("'\u2030"); | ||
| 2479 | continue; | ||
| 2480 | } else if (ch == minus) { | ||
| 2481 | affix.append("'-"); | ||
| 2482 | continue; | ||
| 2483 | } | ||
| 2484 | } | ||
| 2485 | // Note that if we are within quotes, or if this is an | ||
| 2486 | // unquoted, non-special character, then we usually fall | ||
| 2487 | // through to here. | ||
| 2488 | affix.append(ch); | ||
| 2489 | break; | ||
| 2490 | |||
| 2491 | case 1: | ||
| 2492 | // Phase one must be identical in the two sub-patterns. We | ||
| 2493 | // enforce this by doing a direct comparison. While | ||
| 2494 | // processing the first sub-pattern, we just record its | ||
| 2495 | // length. While processing the second, we compare | ||
| 2496 | // characters. | ||
| 2497 | if (j == 1) { | ||
| 2498 | ++phaseOneLength; | ||
| 2499 | } else { | ||
| 2500 | if (--phaseOneLength == 0) { | ||
| 2501 | phase = 2; | ||
| 2502 | affix = suffix; | ||
| 2503 | } | ||
| 2504 | continue; | ||
| 2505 | } | ||
| 2506 | |||
| 2507 | // Process the digits, decimal, and grouping characters. We | ||
| 2508 | // record five pieces of information. We expect the digits | ||
| 2509 | // to occur in the pattern ####0000.####, and we record the | ||
| 2510 | // number of left digits, zero (central) digits, and right | ||
| 2511 | // digits. The position of the last grouping character is | ||
| 2512 | // recorded (should be somewhere within the first two blocks | ||
| 2513 | // of characters), as is the position of the decimal point, | ||
| 2514 | // if any (should be in the zero digits). If there is no | ||
| 2515 | // decimal point, then there should be no right digits. | ||
| 2516 | if (ch == digit) { | ||
| 2517 | if (zeroDigitCount > 0) { | ||
| 2518 | ++digitRightCount; | ||
| 2519 | } else { | ||
| 2520 | ++digitLeftCount; | ||
| 2521 | } | ||
| 2522 | if (groupingCount >= 0 && decimalPos < 0) { | ||
| 2523 | ++groupingCount; | ||
| 2524 | } | ||
| 2525 | } else if (ch == zeroDigit) { | ||
| 2526 | if (digitRightCount > 0) { | ||
| 2527 | throw new IllegalArgumentException("Unexpected '0' in pattern \"" + | ||
| 2528 | pattern + '"'); | ||
| 2529 | } | ||
| 2530 | ++zeroDigitCount; | ||
| 2531 | if (groupingCount >= 0 && decimalPos < 0) { | ||
| 2532 | ++groupingCount; | ||
| 2533 | } | ||
| 2534 | } else if (ch == groupingSeparator) { | ||
| 2535 | groupingCount = 0; | ||
| 2536 | } else if (ch == decimalSeparator) { | ||
| 2537 | if (decimalPos >= 0) { | ||
| 2538 | throw new IllegalArgumentException("Multiple decimal separators in pattern \"" + | ||
| 2539 | pattern + '"'); | ||
| 2540 | } | ||
| 2541 | decimalPos = digitLeftCount + zeroDigitCount + digitRightCount; | ||
| 2542 | } else if (pattern.regionMatches(pos, exponent, 0, exponent.length())){ | ||
| 2543 | if (useExponentialNotation) { | ||
| 2544 | throw new IllegalArgumentException("Multiple exponential " + | ||
| 2545 | "symbols in pattern \"" + pattern + '"'); | ||
| 2546 | } | ||
| 2547 | useExponentialNotation = true; | ||
| 2548 | minExponentDigits = 0; | ||
| 2549 | |||
| 2550 | // Use lookahead to parse out the exponential part | ||
| 2551 | // of the pattern, then jump into phase 2. | ||
| 2552 | pos = pos+exponent.length(); | ||
| 2553 | while (pos < pattern.length() && | ||
| 2554 | pattern.charAt(pos) == zeroDigit) { | ||
| 2555 | ++minExponentDigits; | ||
| 2556 | ++phaseOneLength; | ||
| 2557 | ++pos; | ||
| 2558 | } | ||
| 2559 | |||
| 2560 | if ((digitLeftCount + zeroDigitCount) < 1 || | ||
| 2561 | minExponentDigits < 1) { | ||
| 2562 | throw new IllegalArgumentException("Malformed exponential " + | ||
| 2563 | "pattern \"" + pattern + '"'); | ||
| 2564 | } | ||
| 2565 | |||
| 2566 | // Transition to phase 2 | ||
| 2567 | phase = 2; | ||
| 2568 | affix = suffix; | ||
| 2569 | --pos; | ||
| 2570 | continue; | ||
| 2571 | } else { | ||
| 2572 | phase = 2; | ||
| 2573 | affix = suffix; | ||
| 2574 | --pos; | ||
| 2575 | --phaseOneLength; | ||
| 2576 | continue; | ||
| 2577 | } | ||
| 2578 | break; | ||
| 2579 | } | ||
| 2580 | } | ||
| 2581 | |||
| 2582 | // Handle patterns with no '0' pattern character. These patterns | ||
| 2583 | // are legal, but must be interpreted. "##.###" -> "#0.###". | ||
| 2584 | // ".###" -> ".0##". | ||
| 2585 | /* We allow patterns of the form "####" to produce a zeroDigitCount | ||
| 2586 | * of zero (got that?); although this seems like it might make it | ||
| 2587 | * possible for format() to produce empty strings, format() checks | ||
| 2588 | * for this condition and outputs a zero digit in this situation. | ||
| 2589 | * Having a zeroDigitCount of zero yields a minimum integer digits | ||
| 2590 | * of zero, which allows proper round-trip patterns. That is, we | ||
| 2591 | * don't want "#" to become "#0" when toPattern() is called (even | ||
| 2592 | * though that's what it really is, semantically). | ||
| 2593 | */ | ||
| 2594 | if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) { | ||
| 2595 | // Handle "###.###" and "###." and ".###" | ||
| 2596 | int n = decimalPos; | ||
| 2597 | if (n == 0) { // Handle ".###" | ||
| 2598 | ++n; | ||
| 2599 | } | ||
| 2600 | digitRightCount = digitLeftCount - n; | ||
| 2601 | digitLeftCount = n - 1; | ||
| 2602 | zeroDigitCount = 1; | ||
| 2603 | } | ||
| 2604 | |||
| 2605 | // Do syntax checking on the digits. | ||
| 2606 | if ((decimalPos < 0 && digitRightCount > 0) || | ||
| 2607 | (decimalPos >= 0 && (decimalPos < digitLeftCount || | ||
| 2608 | decimalPos > (digitLeftCount + zeroDigitCount))) || | ||
| 2609 | groupingCount == 0 || inQuote) { | ||
| 2610 | throw new IllegalArgumentException("Malformed pattern \"" + | ||
| 2611 | pattern + '"'); | ||
| 2612 | } | ||
| 2613 | |||
| 2614 | if (j == 1) { | ||
| 2615 | posPrefixPattern = prefix.toString(); | ||
| 2616 | posSuffixPattern = suffix.toString(); | ||
| 2617 | negPrefixPattern = posPrefixPattern; // assume these for now | ||
| 2618 | negSuffixPattern = posSuffixPattern; | ||
| 2619 | int digitTotalCount = digitLeftCount + zeroDigitCount + digitRightCount; | ||
| 2620 | /* The effectiveDecimalPos is the position the decimal is at or | ||
| 2621 | * would be at if there is no decimal. Note that if decimalPos<0, | ||
| 2622 | * then digitTotalCount == digitLeftCount + zeroDigitCount. | ||
| 2623 | */ | ||
| 2624 | int effectiveDecimalPos = decimalPos >= 0 ? | ||
| 2625 | decimalPos : digitTotalCount; | ||
| 2626 | setMinimumIntegerDigits(effectiveDecimalPos - digitLeftCount); | ||
| 2627 | setMaximumIntegerDigits(useExponentialNotation ? | ||
| 2628 | digitLeftCount + getMinimumIntegerDigits() : | ||
| 2629 | MAXIMUM_INTEGER_DIGITS); | ||
| 2630 | setMaximumFractionDigits(decimalPos >= 0 ? | ||
| 2631 | (digitTotalCount - decimalPos) : 0); | ||
| 2632 | setMinimumFractionDigits(decimalPos >= 0 ? | ||
| 2633 | (digitLeftCount + zeroDigitCount - decimalPos) : 0); | ||
| 2634 | setGroupingUsed(groupingCount > 0); | ||
| 2635 | this.groupingSize = (groupingCount > 0) ? groupingCount : 0; | ||
| 2636 | this.multiplier = multiplier; | ||
| 2637 | setDecimalSeparatorAlwaysShown(decimalPos == 0 || | ||
| 2638 | decimalPos == digitTotalCount); | ||
| 2639 | } else { | ||
| 2640 | negPrefixPattern = prefix.toString(); | ||
| 2641 | negSuffixPattern = suffix.toString(); | ||
| 2642 | gotNegative = true; | ||
| 2643 | } | ||
| 2644 | } | ||
| 2645 | |||
| 2646 | if (pattern.length() == 0) { | ||
| 2647 | posPrefixPattern = posSuffixPattern = ""; | ||
| 2648 | setMinimumIntegerDigits(0); | ||
| 2649 | setMaximumIntegerDigits(MAXIMUM_INTEGER_DIGITS); | ||
| 2650 | setMinimumFractionDigits(0); | ||
| 2651 | setMaximumFractionDigits(MAXIMUM_FRACTION_DIGITS); | ||
| 2652 | } | ||
| 2653 | |||
| 2654 | // If there was no negative pattern, or if the negative pattern is | ||
| 2655 | // identical to the positive pattern, then prepend the minus sign to | ||
| 2656 | // the positive pattern to form the negative pattern. | ||
| 2657 | if (!gotNegative || | ||
| 2658 | (negPrefixPattern.equals(posPrefixPattern) | ||
| 2659 | && negSuffixPattern.equals(posSuffixPattern))) { | ||
| 2660 | negSuffixPattern = posSuffixPattern; | ||
| 2661 | negPrefixPattern = "'-" + posPrefixPattern; | ||
| 2662 | } | ||
| 2663 | |||
| 2664 | expandAffixes(); | ||
| 2665 | } | ||
| 2666 | |||
| 2667 | /** | ||
| 2668 | * Sets the maximum number of digits allowed in the integer portion of a | ||
| 2669 | * number. | ||
| 2670 | * For formatting numbers other than <code>BigInteger</code> and | ||
| 2671 | * <code>BigDecimal</code> objects, the lower of <code>newValue</code> and | ||
| 2672 | * 309 is used. Negative input values are replaced with 0. | ||
| 2673 | * @see NumberFormat#setMaximumIntegerDigits | ||
| 2674 | */ | ||
| 2675 | public void setMaximumIntegerDigits(int newValue) { | ||
| 2676 | maximumIntegerDigits = Math.min(Math.max(0, newValue), MAXIMUM_INTEGER_DIGITS); | ||
| 2677 | super.setMaximumIntegerDigits((maximumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? | ||
| 2678 | DOUBLE_INTEGER_DIGITS : maximumIntegerDigits); | ||
| 2679 | if (minimumIntegerDigits > maximumIntegerDigits) { | ||
| 2680 | minimumIntegerDigits = maximumIntegerDigits; | ||
| 2681 | super.setMinimumIntegerDigits((minimumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? | ||
| 2682 | DOUBLE_INTEGER_DIGITS : minimumIntegerDigits); | ||
| 2683 | } | ||
| 2684 | } | ||
| 2685 | |||
| 2686 | /** | ||
| 2687 | * Sets the minimum number of digits allowed in the integer portion of a | ||
| 2688 | * number. | ||
| 2689 | * For formatting numbers other than <code>BigInteger</code> and | ||
| 2690 | * <code>BigDecimal</code> objects, the lower of <code>newValue</code> and | ||
| 2691 | * 309 is used. Negative input values are replaced with 0. | ||
| 2692 | * @see NumberFormat#setMinimumIntegerDigits | ||
| 2693 | */ | ||
| 2694 | public void setMinimumIntegerDigits(int newValue) { | ||
| 2695 | minimumIntegerDigits = Math.min(Math.max(0, newValue), MAXIMUM_INTEGER_DIGITS); | ||
| 2696 | super.setMinimumIntegerDigits((minimumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? | ||
| 2697 | DOUBLE_INTEGER_DIGITS : minimumIntegerDigits); | ||
| 2698 | if (minimumIntegerDigits > maximumIntegerDigits) { | ||
| 2699 | maximumIntegerDigits = minimumIntegerDigits; | ||
| 2700 | super.setMaximumIntegerDigits((maximumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? | ||
| 2701 | DOUBLE_INTEGER_DIGITS : maximumIntegerDigits); | ||
| 2702 | } | ||
| 2703 | } | ||
| 2704 | |||
| 2705 | /** | ||
| 2706 | * Sets the maximum number of digits allowed in the fraction portion of a | ||
| 2707 | * number. | ||
| 2708 | * For formatting numbers other than <code>BigInteger</code> and | ||
| 2709 | * <code>BigDecimal</code> objects, the lower of <code>newValue</code> and | ||
| 2710 | * 340 is used. Negative input values are replaced with 0. | ||
| 2711 | * @see NumberFormat#setMaximumFractionDigits | ||
| 2712 | */ | ||
| 2713 | public void setMaximumFractionDigits(int newValue) { | ||
| 2714 | maximumFractionDigits = Math.min(Math.max(0, newValue), MAXIMUM_FRACTION_DIGITS); | ||
| 2715 | super.setMaximumFractionDigits((maximumFractionDigits > DOUBLE_FRACTION_DIGITS) ? | ||
| 2716 | DOUBLE_FRACTION_DIGITS : maximumFractionDigits); | ||
| 2717 | if (minimumFractionDigits > maximumFractionDigits) { | ||
| 2718 | minimumFractionDigits = maximumFractionDigits; | ||
| 2719 | super.setMinimumFractionDigits((minimumFractionDigits > DOUBLE_FRACTION_DIGITS) ? | ||
| 2720 | DOUBLE_FRACTION_DIGITS : minimumFractionDigits); | ||
| 2721 | } | ||
| 2722 | } | ||
| 2723 | |||
| 2724 | /** | ||
| 2725 | * Sets the minimum number of digits allowed in the fraction portion of a | ||
| 2726 | * number. | ||
| 2727 | * For formatting numbers other than <code>BigInteger</code> and | ||
| 2728 | * <code>BigDecimal</code> objects, the lower of <code>newValue</code> and | ||
| 2729 | * 340 is used. Negative input values are replaced with 0. | ||
| 2730 | * @see NumberFormat#setMinimumFractionDigits | ||
| 2731 | */ | ||
| 2732 | public void setMinimumFractionDigits(int newValue) { | ||
| 2733 | minimumFractionDigits = Math.min(Math.max(0, newValue), MAXIMUM_FRACTION_DIGITS); | ||
| 2734 | super.setMinimumFractionDigits((minimumFractionDigits > DOUBLE_FRACTION_DIGITS) ? | ||
| 2735 | DOUBLE_FRACTION_DIGITS : minimumFractionDigits); | ||
| 2736 | if (minimumFractionDigits > maximumFractionDigits) { | ||
| 2737 | maximumFractionDigits = minimumFractionDigits; | ||
| 2738 | super.setMaximumFractionDigits((maximumFractionDigits > DOUBLE_FRACTION_DIGITS) ? | ||
| 2739 | DOUBLE_FRACTION_DIGITS : maximumFractionDigits); | ||
| 2740 | } | ||
| 2741 | } | ||
| 2742 | |||
| 2743 | /** | ||
| 2744 | * Gets the maximum number of digits allowed in the integer portion of a | ||
| 2745 | * number. | ||
| 2746 | * For formatting numbers other than <code>BigInteger</code> and | ||
| 2747 | * <code>BigDecimal</code> objects, the lower of the return value and | ||
| 2748 | * 309 is used. | ||
| 2749 | * @see #setMaximumIntegerDigits | ||
| 2750 | */ | ||
| 2751 | public int getMaximumIntegerDigits() { | ||
| 2752 | return maximumIntegerDigits; | ||
| 2753 | } | ||
| 2754 | |||
| 2755 | /** | ||
| 2756 | * Gets the minimum number of digits allowed in the integer portion of a | ||
| 2757 | * number. | ||
| 2758 | * For formatting numbers other than <code>BigInteger</code> and | ||
| 2759 | * <code>BigDecimal</code> objects, the lower of the return value and | ||
| 2760 | * 309 is used. | ||
| 2761 | * @see #setMinimumIntegerDigits | ||
| 2762 | */ | ||
| 2763 | public int getMinimumIntegerDigits() { | ||
| 2764 | return minimumIntegerDigits; | ||
| 2765 | } | ||
| 2766 | |||
| 2767 | /** | ||
| 2768 | * Gets the maximum number of digits allowed in the fraction portion of a | ||
| 2769 | * number. | ||
| 2770 | * For formatting numbers other than <code>BigInteger</code> and | ||
| 2771 | * <code>BigDecimal</code> objects, the lower of the return value and | ||
| 2772 | * 340 is used. | ||
| 2773 | * @see #setMaximumFractionDigits | ||
| 2774 | */ | ||
| 2775 | public int getMaximumFractionDigits() { | ||
| 2776 | return maximumFractionDigits; | ||
| 2777 | } | ||
| 2778 | |||
| 2779 | /** | ||
| 2780 | * Gets the minimum number of digits allowed in the fraction portion of a | ||
| 2781 | * number. | ||
| 2782 | * For formatting numbers other than <code>BigInteger</code> and | ||
| 2783 | * <code>BigDecimal</code> objects, the lower of the return value and | ||
| 2784 | * 340 is used. | ||
| 2785 | * @see #setMinimumFractionDigits | ||
| 2786 | */ | ||
| 2787 | public int getMinimumFractionDigits() { | ||
| 2788 | return minimumFractionDigits; | ||
| 2789 | } | ||
| 2790 | |||
| 2791 | /** | ||
| 2792 | * Gets the currency used by this decimal format when formatting | ||
| 2793 | * currency values. | ||
| 2794 | * The currency is obtained by calling | ||
| 2795 | * {@link DecimalFormatSymbols#getCurrency DecimalFormatSymbols.getCurrency} | ||
| 2796 | * on this number format's symbols. | ||
| 2797 | * | ||
| 2798 | * @return the currency used by this decimal format, or <code>null</code> | ||
| 2799 | * @since 1.4 | ||
| 2800 | */ | ||
| 2801 | public Currency getCurrency() { | ||
| 2802 | return symbols.getCurrency(); | ||
| 2803 | } | ||
| 2804 | |||
| 2805 | /** | ||
| 2806 | * Sets the currency used by this number format when formatting | ||
| 2807 | * currency values. This does not update the minimum or maximum | ||
| 2808 | * number of fraction digits used by the number format. | ||
| 2809 | * The currency is set by calling | ||
| 2810 | * {@link DecimalFormatSymbols#setCurrency DecimalFormatSymbols.setCurrency} | ||
| 2811 | * on this number format's symbols. | ||
| 2812 | * | ||
| 2813 | * @param currency the new currency to be used by this decimal format | ||
| 2814 | * @exception NullPointerException if <code>currency</code> is null | ||
| 2815 | * @since 1.4 | ||
| 2816 | */ | ||
| 2817 | public void setCurrency(Currency currency) { | ||
| 2818 | if (currency != symbols.getCurrency()) { | ||
| 2819 | symbols.setCurrency(currency); | ||
| 2820 | if (isCurrencyFormat) { | ||
| 2821 | expandAffixes(); | ||
| 2822 | } | ||
| 2823 | } | ||
| 2824 | } | ||
| 2825 | |||
| 2826 | /** | ||
| 2827 | * Gets the {@link java.math.RoundingMode} used in this DecimalFormat. | ||
| 2828 | * | ||
| 2829 | * @return The <code>RoundingMode</code> used for this DecimalFormat. | ||
| 2830 | * @see #setRoundingMode(RoundingMode) | ||
| 2831 | * @since 1.6 | ||
| 2832 | */ | ||
| 2833 | public RoundingMode getRoundingMode() { | ||
| 2834 | return roundingMode; | ||
| 2835 | } | ||
| 2836 | |||
| 2837 | /** | ||
| 2838 | * Sets the {@link java.math.RoundingMode} used in this DecimalFormat. | ||
| 2839 | * | ||
| 2840 | * @param roundingMode The <code>RoundingMode</code> to be used | ||
| 2841 | * @see #getRoundingMode() | ||
| 2842 | * @exception NullPointerException if <code>roundingMode</code> is null. | ||
| 2843 | * @since 1.6 | ||
| 2844 | */ | ||
| 2845 | public void setRoundingMode(RoundingMode roundingMode) { | ||
| 2846 | if (roundingMode == null) { | ||
| 2847 | throw new NullPointerException(); | ||
| 2848 | } | ||
| 2849 | |||
| 2850 | this.roundingMode = roundingMode; | ||
| 2851 | digitList.setRoundingMode(roundingMode); | ||
| 2852 | } | ||
| 2853 | |||
| 2854 | /** | ||
| 2855 | * Adjusts the minimum and maximum fraction digits to values that | ||
| 2856 | * are reasonable for the currency's default fraction digits. | ||
| 2857 | */ | ||
| 2858 | void adjustForCurrencyDefaultFractionDigits() { | ||
| 2859 | Currency currency = symbols.getCurrency(); | ||
| 2860 | if (currency == null) { | ||
| 2861 | try { | ||
| 2862 | currency = Currency.getInstance(symbols.getInternationalCurrencySymbol()); | ||
| 2863 | } catch (IllegalArgumentException e) { | ||
| 2864 | } | ||
| 2865 | } | ||
| 2866 | if (currency != null) { | ||
| 2867 | int digits = currency.getDefaultFractionDigits(); | ||
| 2868 | if (digits != -1) { | ||
| 2869 | int oldMinDigits = getMinimumFractionDigits(); | ||
| 2870 | // Common patterns are "#.##", "#.00", "#". | ||
| 2871 | // Try to adjust all of them in a reasonable way. | ||
| 2872 | if (oldMinDigits == getMaximumFractionDigits()) { | ||
| 2873 | setMinimumFractionDigits(digits); | ||
| 2874 | setMaximumFractionDigits(digits); | ||
| 2875 | } else { | ||
| 2876 | setMinimumFractionDigits(Math.min(digits, oldMinDigits)); | ||
| 2877 | setMaximumFractionDigits(digits); | ||
| 2878 | } | ||
| 2879 | } | ||
| 2880 | } | ||
| 2881 | } | ||
| 2882 | |||
| 2883 | /** | ||
| 2884 | * Reads the default serializable fields from the stream and performs | ||
| 2885 | * validations and adjustments for older serialized versions. The | ||
| 2886 | * validations and adjustments are: | ||
| 2887 | * <ol> | ||
| 2888 | * <li> | ||
| 2889 | * Verify that the superclass's digit count fields correctly reflect | ||
| 2890 | * the limits imposed on formatting numbers other than | ||
| 2891 | * <code>BigInteger</code> and <code>BigDecimal</code> objects. These | ||
| 2892 | * limits are stored in the superclass for serialization compatibility | ||
| 2893 | * with older versions, while the limits for <code>BigInteger</code> and | ||
| 2894 | * <code>BigDecimal</code> objects are kept in this class. | ||
| 2895 | * If, in the superclass, the minimum or maximum integer digit count is | ||
| 2896 | * larger than <code>DOUBLE_INTEGER_DIGITS</code> or if the minimum or | ||
| 2897 | * maximum fraction digit count is larger than | ||
| 2898 | * <code>DOUBLE_FRACTION_DIGITS</code>, then the stream data is invalid | ||
| 2899 | * and this method throws an <code>InvalidObjectException</code>. | ||
| 2900 | * <li> | ||
| 2901 | * If <code>serialVersionOnStream</code> is less than 4, initialize | ||
| 2902 | * <code>roundingMode</code> to {@link java.math.RoundingMode#HALF_EVEN | ||
| 2903 | * RoundingMode.HALF_EVEN}. This field is new with version 4. | ||
| 2904 | * <li> | ||
| 2905 | * If <code>serialVersionOnStream</code> is less than 3, then call | ||
| 2906 | * the setters for the minimum and maximum integer and fraction digits with | ||
| 2907 | * the values of the corresponding superclass getters to initialize the | ||
| 2908 | * fields in this class. The fields in this class are new with version 3. | ||
| 2909 | * <li> | ||
| 2910 | * If <code>serialVersionOnStream</code> is less than 1, indicating that | ||
| 2911 | * the stream was written by JDK 1.1, initialize | ||
| 2912 | * <code>useExponentialNotation</code> | ||
| 2913 | * to false, since it was not present in JDK 1.1. | ||
| 2914 | * <li> | ||
| 2915 | * Set <code>serialVersionOnStream</code> to the maximum allowed value so | ||
| 2916 | * that default serialization will work properly if this object is streamed | ||
| 2917 | * out again. | ||
| 2918 | * </ol> | ||
| 2919 | * | ||
| 2920 | * <p>Stream versions older than 2 will not have the affix pattern variables | ||
| 2921 | * <code>posPrefixPattern</code> etc. As a result, they will be initialized | ||
| 2922 | * to <code>null</code>, which means the affix strings will be taken as | ||
| 2923 | * literal values. This is exactly what we want, since that corresponds to | ||
| 2924 | * the pre-version-2 behavior. | ||
| 2925 | */ | ||
| 2926 | private void readObject(ObjectInputStream stream) | ||
| 2927 | throws IOException, ClassNotFoundException | ||
| 2928 | { | ||
| 2929 | stream.defaultReadObject(); | ||
| 2930 | digitList = new DigitList(); | ||
| 2931 | |||
| 2932 | if (serialVersionOnStream < 4) { | ||
| 2933 | setRoundingMode(RoundingMode.HALF_EVEN); | ||
| 2934 | } | ||
| 2935 | // We only need to check the maximum counts because NumberFormat | ||
| 2936 | // .readObject has already ensured that the maximum is greater than the | ||
| 2937 | // minimum count. | ||
| 2938 | if (super.getMaximumIntegerDigits() > DOUBLE_INTEGER_DIGITS || | ||
| 2939 | super.getMaximumFractionDigits() > DOUBLE_FRACTION_DIGITS) { | ||
| 2940 | throw new InvalidObjectException("Digit count out of range"); | ||
| 2941 | } | ||
| 2942 | if (serialVersionOnStream < 3) { | ||
| 2943 | setMaximumIntegerDigits(super.getMaximumIntegerDigits()); | ||
| 2944 | setMinimumIntegerDigits(super.getMinimumIntegerDigits()); | ||
| 2945 | setMaximumFractionDigits(super.getMaximumFractionDigits()); | ||
| 2946 | setMinimumFractionDigits(super.getMinimumFractionDigits()); | ||
| 2947 | } | ||
| 2948 | if (serialVersionOnStream < 1) { | ||
| 2949 | // Didn't have exponential fields | ||
| 2950 | useExponentialNotation = false; | ||
| 2951 | } | ||
| 2952 | serialVersionOnStream = currentSerialVersion; | ||
| 2953 | } | ||
| 2954 | |||
| 2955 | //---------------------------------------------------------------------- | ||
| 2956 | // INSTANCE VARIABLES | ||
| 2957 | //---------------------------------------------------------------------- | ||
| 2958 | |||
| 2959 | private transient DigitList digitList = new DigitList(); | ||
| 2960 | |||
| 2961 | /** | ||
| 2962 | * The symbol used as a prefix when formatting positive numbers, e.g. "+". | ||
| 2963 | * | ||
| 2964 | * @serial | ||
| 2965 | * @see #getPositivePrefix | ||
| 2966 | */ | ||
| 2967 | private String positivePrefix = ""; | ||
| 2968 | |||
| 2969 | /** | ||
| 2970 | * The symbol used as a suffix when formatting positive numbers. | ||
| 2971 | * This is often an empty string. | ||
| 2972 | * | ||
| 2973 | * @serial | ||
| 2974 | * @see #getPositiveSuffix | ||
| 2975 | */ | ||
| 2976 | private String positiveSuffix = ""; | ||
| 2977 | |||
| 2978 | /** | ||
| 2979 | * The symbol used as a prefix when formatting negative numbers, e.g. "-". | ||
| 2980 | * | ||
| 2981 | * @serial | ||
| 2982 | * @see #getNegativePrefix | ||
| 2983 | */ | ||
| 2984 | private String negativePrefix = "-"; | ||
| 2985 | |||
| 2986 | /** | ||
| 2987 | * The symbol used as a suffix when formatting negative numbers. | ||
| 2988 | * This is often an empty string. | ||
| 2989 | * | ||
| 2990 | * @serial | ||
| 2991 | * @see #getNegativeSuffix | ||
| 2992 | */ | ||
| 2993 | private String negativeSuffix = ""; | ||
| 2994 | |||
| 2995 | /** | ||
| 2996 | * The prefix pattern for non-negative numbers. This variable corresponds | ||
| 2997 | * to <code>positivePrefix</code>. | ||
| 2998 | * | ||
| 2999 | * <p>This pattern is expanded by the method <code>expandAffix()</code> to | ||
| 3000 | * <code>positivePrefix</code> to update the latter to reflect changes in | ||
| 3001 | * <code>symbols</code>. If this variable is <code>null</code> then | ||
| 3002 | * <code>positivePrefix</code> is taken as a literal value that does not | ||
| 3003 | * change when <code>symbols</code> changes. This variable is always | ||
| 3004 | * <code>null</code> for <code>DecimalFormat</code> objects older than | ||
| 3005 | * stream version 2 restored from stream. | ||
| 3006 | * | ||
| 3007 | * @serial | ||
| 3008 | * @since 1.3 | ||
| 3009 | */ | ||
| 3010 | private String posPrefixPattern; | ||
| 3011 | |||
| 3012 | /** | ||
| 3013 | * The suffix pattern for non-negative numbers. This variable corresponds | ||
| 3014 | * to <code>positiveSuffix</code>. This variable is analogous to | ||
| 3015 | * <code>posPrefixPattern</code>; see that variable for further | ||
| 3016 | * documentation. | ||
| 3017 | * | ||
| 3018 | * @serial | ||
| 3019 | * @since 1.3 | ||
| 3020 | */ | ||
| 3021 | private String posSuffixPattern; | ||
| 3022 | |||
| 3023 | /** | ||
| 3024 | * The prefix pattern for negative numbers. This variable corresponds | ||
| 3025 | * to <code>negativePrefix</code>. This variable is analogous to | ||
| 3026 | * <code>posPrefixPattern</code>; see that variable for further | ||
| 3027 | * documentation. | ||
| 3028 | * | ||
| 3029 | * @serial | ||
| 3030 | * @since 1.3 | ||
| 3031 | */ | ||
| 3032 | private String negPrefixPattern; | ||
| 3033 | |||
| 3034 | /** | ||
| 3035 | * The suffix pattern for negative numbers. This variable corresponds | ||
| 3036 | * to <code>negativeSuffix</code>. This variable is analogous to | ||
| 3037 | * <code>posPrefixPattern</code>; see that variable for further | ||
| 3038 | * documentation. | ||
| 3039 | * | ||
| 3040 | * @serial | ||
| 3041 | * @since 1.3 | ||
| 3042 | */ | ||
| 3043 | private String negSuffixPattern; | ||
| 3044 | |||
| 3045 | /** | ||
| 3046 | * The multiplier for use in percent, per mille, etc. | ||
| 3047 | * | ||
| 3048 | * @serial | ||
| 3049 | * @see #getMultiplier | ||
| 3050 | */ | ||
| 3051 | private int multiplier = 1; | ||
| 3052 | |||
| 3053 | /** | ||
| 3054 | * The number of digits between grouping separators in the integer | ||
| 3055 | * portion of a number. Must be greater than 0 if | ||
| 3056 | * <code>NumberFormat.groupingUsed</code> is true. | ||
| 3057 | * | ||
| 3058 | * @serial | ||
| 3059 | * @see #getGroupingSize | ||
| 3060 | * @see java.text.NumberFormat#isGroupingUsed | ||
| 3061 | */ | ||
| 3062 | private byte groupingSize = 3; // invariant, > 0 if useThousands | ||
| 3063 | |||
| 3064 | /** | ||
| 3065 | * If true, forces the decimal separator to always appear in a formatted | ||
| 3066 | * number, even if the fractional part of the number is zero. | ||
| 3067 | * | ||
| 3068 | * @serial | ||
| 3069 | * @see #isDecimalSeparatorAlwaysShown | ||
| 3070 | */ | ||
| 3071 | private boolean decimalSeparatorAlwaysShown = false; | ||
| 3072 | |||
| 3073 | /** | ||
| 3074 | * If true, parse returns BigDecimal wherever possible. | ||
| 3075 | * | ||
| 3076 | * @serial | ||
| 3077 | * @see #isParseBigDecimal | ||
| 3078 | * @since 1.5 | ||
| 3079 | */ | ||
| 3080 | private boolean parseBigDecimal = false; | ||
| 3081 | |||
| 3082 | |||
| 3083 | /** | ||
| 3084 | * True if this object represents a currency format. This determines | ||
| 3085 | * whether the monetary decimal separator is used instead of the normal one. | ||
| 3086 | */ | ||
| 3087 | private transient boolean isCurrencyFormat = false; | ||
| 3088 | |||
| 3089 | /** | ||
| 3090 | * The <code>DecimalFormatSymbols</code> object used by this format. | ||
| 3091 | * It contains the symbols used to format numbers, e.g. the grouping separator, | ||
| 3092 | * decimal separator, and so on. | ||
| 3093 | * | ||
| 3094 | * @serial | ||
| 3095 | * @see #setDecimalFormatSymbols | ||
| 3096 | * @see java.text.DecimalFormatSymbols | ||
| 3097 | */ | ||
| 3098 | private DecimalFormatSymbols symbols = null; // LIU new DecimalFormatSymbols(); | ||
| 3099 | |||
| 3100 | /** | ||
| 3101 | * True to force the use of exponential (i.e. scientific) notation when formatting | ||
| 3102 | * numbers. | ||
| 3103 | * | ||
| 3104 | * @serial | ||
| 3105 | * @since 1.2 | ||
| 3106 | */ | ||
| 3107 | private boolean useExponentialNotation; // Newly persistent in the Java 2 platform v.1.2 | ||
| 3108 | |||
| 3109 | /** | ||
| 3110 | * FieldPositions describing the positive prefix String. This is | ||
| 3111 | * lazily created. Use <code>getPositivePrefixFieldPositions</code> | ||
| 3112 | * when needed. | ||
| 3113 | */ | ||
| 3114 | private transient FieldPosition[] positivePrefixFieldPositions; | ||
| 3115 | |||
| 3116 | /** | ||
| 3117 | * FieldPositions describing the positive suffix String. This is | ||
| 3118 | * lazily created. Use <code>getPositiveSuffixFieldPositions</code> | ||
| 3119 | * when needed. | ||
| 3120 | */ | ||
| 3121 | private transient FieldPosition[] positiveSuffixFieldPositions; | ||
| 3122 | |||
| 3123 | /** | ||
| 3124 | * FieldPositions describing the negative prefix String. This is | ||
| 3125 | * lazily created. Use <code>getNegativePrefixFieldPositions</code> | ||
| 3126 | * when needed. | ||
| 3127 | */ | ||
| 3128 | private transient FieldPosition[] negativePrefixFieldPositions; | ||
| 3129 | |||
| 3130 | /** | ||
| 3131 | * FieldPositions describing the negative suffix String. This is | ||
| 3132 | * lazily created. Use <code>getNegativeSuffixFieldPositions</code> | ||
| 3133 | * when needed. | ||
| 3134 | */ | ||
| 3135 | private transient FieldPosition[] negativeSuffixFieldPositions; | ||
| 3136 | |||
| 3137 | /** | ||
| 3138 | * The minimum number of digits used to display the exponent when a number is | ||
| 3139 | * formatted in exponential notation. This field is ignored if | ||
| 3140 | * <code>useExponentialNotation</code> is not true. | ||
| 3141 | * | ||
| 3142 | * @serial | ||
| 3143 | * @since 1.2 | ||
| 3144 | */ | ||
| 3145 | private byte minExponentDigits; // Newly persistent in the Java 2 platform v.1.2 | ||
| 3146 | |||
| 3147 | /** | ||
| 3148 | * The maximum number of digits allowed in the integer portion of a | ||
| 3149 | * <code>BigInteger</code> or <code>BigDecimal</code> number. | ||
| 3150 | * <code>maximumIntegerDigits</code> must be greater than or equal to | ||
| 3151 | * <code>minimumIntegerDigits</code>. | ||
| 3152 | * | ||
| 3153 | * @serial | ||
| 3154 | * @see #getMaximumIntegerDigits | ||
| 3155 | * @since 1.5 | ||
| 3156 | */ | ||
| 3157 | private int maximumIntegerDigits = super.getMaximumIntegerDigits(); | ||
| 3158 | |||
| 3159 | /** | ||
| 3160 | * The minimum number of digits allowed in the integer portion of a | ||
| 3161 | * <code>BigInteger</code> or <code>BigDecimal</code> number. | ||
| 3162 | * <code>minimumIntegerDigits</code> must be less than or equal to | ||
| 3163 | * <code>maximumIntegerDigits</code>. | ||
| 3164 | * | ||
| 3165 | * @serial | ||
| 3166 | * @see #getMinimumIntegerDigits | ||
| 3167 | * @since 1.5 | ||
| 3168 | */ | ||
| 3169 | private int minimumIntegerDigits = super.getMinimumIntegerDigits(); | ||
| 3170 | |||
| 3171 | /** | ||
| 3172 | * The maximum number of digits allowed in the fractional portion of a | ||
| 3173 | * <code>BigInteger</code> or <code>BigDecimal</code> number. | ||
| 3174 | * <code>maximumFractionDigits</code> must be greater than or equal to | ||
| 3175 | * <code>minimumFractionDigits</code>. | ||
| 3176 | * | ||
| 3177 | * @serial | ||
| 3178 | * @see #getMaximumFractionDigits | ||
| 3179 | * @since 1.5 | ||
| 3180 | */ | ||
| 3181 | private int maximumFractionDigits = super.getMaximumFractionDigits(); | ||
| 3182 | |||
| 3183 | /** | ||
| 3184 | * The minimum number of digits allowed in the fractional portion of a | ||
| 3185 | * <code>BigInteger</code> or <code>BigDecimal</code> number. | ||
| 3186 | * <code>minimumFractionDigits</code> must be less than or equal to | ||
| 3187 | * <code>maximumFractionDigits</code>. | ||
| 3188 | * | ||
| 3189 | * @serial | ||
| 3190 | * @see #getMinimumFractionDigits | ||
| 3191 | * @since 1.5 | ||
| 3192 | */ | ||
| 3193 | private int minimumFractionDigits = super.getMinimumFractionDigits(); | ||
| 3194 | |||
| 3195 | /** | ||
| 3196 | * The {@link java.math.RoundingMode} used in this DecimalFormat. | ||
| 3197 | * | ||
| 3198 | * @serial | ||
| 3199 | * @since 1.6 | ||
| 3200 | */ | ||
| 3201 | private RoundingMode roundingMode = RoundingMode.HALF_EVEN; | ||
| 3202 | |||
| 3203 | //---------------------------------------------------------------------- | ||
| 3204 | |||
| 3205 | static final int currentSerialVersion = 4; | ||
| 3206 | |||
| 3207 | /** | ||
| 3208 | * The internal serial version which says which version was written. | ||
| 3209 | * Possible values are: | ||
| 3210 | * <ul> | ||
| 3211 | * <li><b>0</b> (default): versions before the Java 2 platform v1.2 | ||
| 3212 | * <li><b>1</b>: version for 1.2, which includes the two new fields | ||
| 3213 | * <code>useExponentialNotation</code> and | ||
| 3214 | * <code>minExponentDigits</code>. | ||
| 3215 | * <li><b>2</b>: version for 1.3 and later, which adds four new fields: | ||
| 3216 | * <code>posPrefixPattern</code>, <code>posSuffixPattern</code>, | ||
| 3217 | * <code>negPrefixPattern</code>, and <code>negSuffixPattern</code>. | ||
| 3218 | * <li><b>3</b>: version for 1.5 and later, which adds five new fields: | ||
| 3219 | * <code>maximumIntegerDigits</code>, | ||
| 3220 | * <code>minimumIntegerDigits</code>, | ||
| 3221 | * <code>maximumFractionDigits</code>, | ||
| 3222 | * <code>minimumFractionDigits</code>, and | ||
| 3223 | * <code>parseBigDecimal</code>. | ||
| 3224 | * <li><b>4</b>: version for 1.6 and later, which adds one new field: | ||
| 3225 | * <code>roundingMode</code>. | ||
| 3226 | * </ul> | ||
| 3227 | * @since 1.2 | ||
| 3228 | * @serial | ||
| 3229 | */ | ||
| 3230 | private int serialVersionOnStream = currentSerialVersion; | ||
| 3231 | |||
| 3232 | //---------------------------------------------------------------------- | ||
| 3233 | // CONSTANTS | ||
| 3234 | //---------------------------------------------------------------------- | ||
| 3235 | |||
| 3236 | // Constants for characters used in programmatic (unlocalized) patterns. | ||
| 3237 | private static final char PATTERN_ZERO_DIGIT = '0'; | ||
| 3238 | private static final char PATTERN_GROUPING_SEPARATOR = ','; | ||
| 3239 | private static final char PATTERN_DECIMAL_SEPARATOR = '.'; | ||
| 3240 | private static final char PATTERN_PER_MILLE = '\u2030'; | ||
| 3241 | private static final char PATTERN_PERCENT = '%'; | ||
| 3242 | private static final char PATTERN_DIGIT = '#'; | ||
| 3243 | private static final char PATTERN_SEPARATOR = ';'; | ||
| 3244 | private static final String PATTERN_EXPONENT = "E"; | ||
| 3245 | private static final char PATTERN_MINUS = '-'; | ||
| 3246 | |||
| 3247 | /** | ||
| 3248 | * The CURRENCY_SIGN is the standard Unicode symbol for currency. It | ||
| 3249 | * is used in patterns and substituted with either the currency symbol, | ||
| 3250 | * or if it is doubled, with the international currency symbol. If the | ||
| 3251 | * CURRENCY_SIGN is seen in a pattern, then the decimal separator is | ||
| 3252 | * replaced with the monetary decimal separator. | ||
| 3253 | * | ||
| 3254 | * The CURRENCY_SIGN is not localized. | ||
| 3255 | */ | ||
| 3256 | private static final char CURRENCY_SIGN = '\u00A4'; | ||
| 3257 | |||
| 3258 | private static final char QUOTE = '\''; | ||
| 3259 | |||
| 3260 | private static FieldPosition[] EmptyFieldPositionArray = new FieldPosition[0]; | ||
| 3261 | |||
| 3262 | // Upper limit on integer and fraction digits for a Java double | ||
| 3263 | static final int DOUBLE_INTEGER_DIGITS = 309; | ||
| 3264 | static final int DOUBLE_FRACTION_DIGITS = 340; | ||
| 3265 | |||
| 3266 | // Upper limit on integer and fraction digits for BigDecimal and BigInteger | ||
| 3267 | static final int MAXIMUM_INTEGER_DIGITS = Integer.MAX_VALUE; | ||
| 3268 | static final int MAXIMUM_FRACTION_DIGITS = Integer.MAX_VALUE; | ||
| 3269 | |||
| 3270 | // Proclaim JDK 1.1 serial compatibility. | ||
| 3271 | static final long serialVersionUID = 864413376551465018L; | ||
| 3272 | |||
| 3273 | /** | ||
| 3274 | * Cache to hold the NumberPattern of a Locale. | ||
| 3275 | */ | ||
| 3276 | private static final ConcurrentMap<Locale, String> cachedLocaleData | ||
| 3277 | = new ConcurrentHashMap<Locale, String>(3); | ||
| 3278 | } | ||
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,837 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.io.IOException; | ||
| 42 | import java.io.ObjectInputStream; | ||
| 43 | import java.io.Serializable; | ||
| 44 | import java.text.spi.DecimalFormatSymbolsProvider; | ||
| 45 | import java.util.Currency; | ||
| 46 | import java.util.Locale; | ||
| 47 | import java.util.ResourceBundle; | ||
| 48 | import java.util.concurrent.ConcurrentHashMap; | ||
| 49 | |||
| 50 | import sun.util.LocaleServiceProviderPool; | ||
| 51 | import sun.util.resources.LocaleData; | ||
| 52 | |||
| 53 | /** | ||
| 54 | * This class represents the set of symbols (such as the decimal separator, | ||
| 55 | * the grouping separator, and so on) needed by <code>DecimalFormat</code> | ||
| 56 | * to format numbers. <code>DecimalFormat</code> creates for itself an instance of | ||
| 57 | * <code>DecimalFormatSymbols</code> from its locale data. If you need to change any | ||
| 58 | * of these symbols, you can get the <code>DecimalFormatSymbols</code> object from | ||
| 59 | * your <code>DecimalFormat</code> and modify it. | ||
| 60 | * | ||
| 61 | * @see java.util.Locale | ||
| 62 | * @see DecimalFormat | ||
| 63 | * @author Mark Davis | ||
| 64 | * @author Alan Liu | ||
| 65 | */ | ||
| 66 | |||
| 67 | public class DecimalFormatSymbols implements Cloneable, Serializable { | ||
| 68 | |||
| 69 | /** | ||
| 70 | * Create a DecimalFormatSymbols object for the default locale. | ||
| 71 | * This constructor can only construct instances for the locales | ||
| 72 | * supported by the Java runtime environment, not for those | ||
| 73 | * supported by installed | ||
| 74 | * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider} | ||
| 75 | * implementations. For full locale coverage, use the | ||
| 76 | * {@link #getInstance(Locale) getInstance} method. | ||
| 77 | */ | ||
| 78 | public DecimalFormatSymbols() { | ||
| 79 | initialize( Locale.getDefault(Locale.Category.FORMAT) ); | ||
| 80 | } | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Create a DecimalFormatSymbols object for the given locale. | ||
| 84 | * This constructor can only construct instances for the locales | ||
| 85 | * supported by the Java runtime environment, not for those | ||
| 86 | * supported by installed | ||
| 87 | * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider} | ||
| 88 | * implementations. For full locale coverage, use the | ||
| 89 | * {@link #getInstance(Locale) getInstance} method. | ||
| 90 | * | ||
| 91 | * @exception NullPointerException if <code>locale</code> is null | ||
| 92 | */ | ||
| 93 | public DecimalFormatSymbols( Locale locale ) { | ||
| 94 | initialize( locale ); | ||
| 95 | } | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Returns an array of all locales for which the | ||
| 99 | * <code>getInstance</code> methods of this class can return | ||
| 100 | * localized instances. | ||
| 101 | * The returned array represents the union of locales supported by the Java | ||
| 102 | * runtime and by installed | ||
| 103 | * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider} | ||
| 104 | * implementations. It must contain at least a <code>Locale</code> | ||
| 105 | * instance equal to {@link java.util.Locale#US Locale.US}. | ||
| 106 | * | ||
| 107 | * @return An array of locales for which localized | ||
| 108 | * <code>DecimalFormatSymbols</code> instances are available. | ||
| 109 | * @since 1.6 | ||
| 110 | */ | ||
| 111 | public static Locale[] getAvailableLocales() { | ||
| 112 | LocaleServiceProviderPool pool = | ||
| 113 | LocaleServiceProviderPool.getPool(DecimalFormatSymbolsProvider.class); | ||
| 114 | return pool.getAvailableLocales(); | ||
| 115 | } | ||
| 116 | |||
| 117 | /** | ||
| 118 | * Gets the <code>DecimalFormatSymbols</code> instance for the default | ||
| 119 | * locale. This method provides access to <code>DecimalFormatSymbols</code> | ||
| 120 | * instances for locales supported by the Java runtime itself as well | ||
| 121 | * as for those supported by installed | ||
| 122 | * {@link java.text.spi.DecimalFormatSymbolsProvider | ||
| 123 | * DecimalFormatSymbolsProvider} implementations. | ||
| 124 | * @return a <code>DecimalFormatSymbols</code> instance. | ||
| 125 | * @since 1.6 | ||
| 126 | */ | ||
| 127 | public static final DecimalFormatSymbols getInstance() { | ||
| 128 | return getInstance(Locale.getDefault(Locale.Category.FORMAT)); | ||
| 129 | } | ||
| 130 | |||
| 131 | /** | ||
| 132 | * Gets the <code>DecimalFormatSymbols</code> instance for the specified | ||
| 133 | * locale. This method provides access to <code>DecimalFormatSymbols</code> | ||
| 134 | * instances for locales supported by the Java runtime itself as well | ||
| 135 | * as for those supported by installed | ||
| 136 | * {@link java.text.spi.DecimalFormatSymbolsProvider | ||
| 137 | * DecimalFormatSymbolsProvider} implementations. | ||
| 138 | * @param locale the desired locale. | ||
| 139 | * @return a <code>DecimalFormatSymbols</code> instance. | ||
| 140 | * @exception NullPointerException if <code>locale</code> is null | ||
| 141 | * @since 1.6 | ||
| 142 | */ | ||
| 143 | public static final DecimalFormatSymbols getInstance(Locale locale) { | ||
| 144 | |||
| 145 | // Check whether a provider can provide an implementation that's closer | ||
| 146 | // to the requested locale than what the Java runtime itself can provide. | ||
| 147 | LocaleServiceProviderPool pool = | ||
| 148 | LocaleServiceProviderPool.getPool(DecimalFormatSymbolsProvider.class); | ||
| 149 | if (pool.hasProviders()) { | ||
| 150 | DecimalFormatSymbols providersInstance = pool.getLocalizedObject( | ||
| 151 | DecimalFormatSymbolsGetter.INSTANCE, locale); | ||
| 152 | if (providersInstance != null) { | ||
| 153 | return providersInstance; | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | return new DecimalFormatSymbols(locale); | ||
| 158 | } | ||
| 159 | |||
| 160 | /** | ||
| 161 | * Gets the character used for zero. Different for Arabic, etc. | ||
| 162 | */ | ||
| 163 | public char getZeroDigit() { | ||
| 164 | return zeroDigit; | ||
| 165 | } | ||
| 166 | |||
| 167 | /** | ||
| 168 | * Sets the character used for zero. Different for Arabic, etc. | ||
| 169 | */ | ||
| 170 | public void setZeroDigit(char zeroDigit) { | ||
| 171 | this.zeroDigit = zeroDigit; | ||
| 172 | } | ||
| 173 | |||
| 174 | /** | ||
| 175 | * Gets the character used for thousands separator. Different for French, etc. | ||
| 176 | */ | ||
| 177 | public char getGroupingSeparator() { | ||
| 178 | return groupingSeparator; | ||
| 179 | } | ||
| 180 | |||
| 181 | /** | ||
| 182 | * Sets the character used for thousands separator. Different for French, etc. | ||
| 183 | */ | ||
| 184 | public void setGroupingSeparator(char groupingSeparator) { | ||
| 185 | this.groupingSeparator = groupingSeparator; | ||
| 186 | } | ||
| 187 | |||
| 188 | /** | ||
| 189 | * Gets the character used for decimal sign. Different for French, etc. | ||
| 190 | */ | ||
| 191 | public char getDecimalSeparator() { | ||
| 192 | return decimalSeparator; | ||
| 193 | } | ||
| 194 | |||
| 195 | /** | ||
| 196 | * Sets the character used for decimal sign. Different for French, etc. | ||
| 197 | */ | ||
| 198 | public void setDecimalSeparator(char decimalSeparator) { | ||
| 199 | this.decimalSeparator = decimalSeparator; | ||
| 200 | } | ||
| 201 | |||
| 202 | /** | ||
| 203 | * Gets the character used for per mille sign. Different for Arabic, etc. | ||
| 204 | */ | ||
| 205 | public char getPerMill() { | ||
| 206 | return perMill; | ||
| 207 | } | ||
| 208 | |||
| 209 | /** | ||
| 210 | * Sets the character used for per mille sign. Different for Arabic, etc. | ||
| 211 | */ | ||
| 212 | public void setPerMill(char perMill) { | ||
| 213 | this.perMill = perMill; | ||
| 214 | } | ||
| 215 | |||
| 216 | /** | ||
| 217 | * Gets the character used for percent sign. Different for Arabic, etc. | ||
| 218 | */ | ||
| 219 | public char getPercent() { | ||
| 220 | return percent; | ||
| 221 | } | ||
| 222 | |||
| 223 | /** | ||
| 224 | * Sets the character used for percent sign. Different for Arabic, etc. | ||
| 225 | */ | ||
| 226 | public void setPercent(char percent) { | ||
| 227 | this.percent = percent; | ||
| 228 | } | ||
| 229 | |||
| 230 | /** | ||
| 231 | * Gets the character used for a digit in a pattern. | ||
| 232 | */ | ||
| 233 | public char getDigit() { | ||
| 234 | return digit; | ||
| 235 | } | ||
| 236 | |||
| 237 | /** | ||
| 238 | * Sets the character used for a digit in a pattern. | ||
| 239 | */ | ||
| 240 | public void setDigit(char digit) { | ||
| 241 | this.digit = digit; | ||
| 242 | } | ||
| 243 | |||
| 244 | /** | ||
| 245 | * Gets the character used to separate positive and negative subpatterns | ||
| 246 | * in a pattern. | ||
| 247 | */ | ||
| 248 | public char getPatternSeparator() { | ||
| 249 | return patternSeparator; | ||
| 250 | } | ||
| 251 | |||
| 252 | /** | ||
| 253 | * Sets the character used to separate positive and negative subpatterns | ||
| 254 | * in a pattern. | ||
| 255 | */ | ||
| 256 | public void setPatternSeparator(char patternSeparator) { | ||
| 257 | this.patternSeparator = patternSeparator; | ||
| 258 | } | ||
| 259 | |||
| 260 | /** | ||
| 261 | * Gets the string used to represent infinity. Almost always left | ||
| 262 | * unchanged. | ||
| 263 | */ | ||
| 264 | public String getInfinity() { | ||
| 265 | return infinity; | ||
| 266 | } | ||
| 267 | |||
| 268 | /** | ||
| 269 | * Sets the string used to represent infinity. Almost always left | ||
| 270 | * unchanged. | ||
| 271 | */ | ||
| 272 | public void setInfinity(String infinity) { | ||
| 273 | this.infinity = infinity; | ||
| 274 | } | ||
| 275 | |||
| 276 | /** | ||
| 277 | * Gets the string used to represent "not a number". Almost always left | ||
| 278 | * unchanged. | ||
| 279 | */ | ||
| 280 | public String getNaN() { | ||
| 281 | return NaN; | ||
| 282 | } | ||
| 283 | |||
| 284 | /** | ||
| 285 | * Sets the string used to represent "not a number". Almost always left | ||
| 286 | * unchanged. | ||
| 287 | */ | ||
| 288 | public void setNaN(String NaN) { | ||
| 289 | this.NaN = NaN; | ||
| 290 | } | ||
| 291 | |||
| 292 | /** | ||
| 293 | * Gets the character used to represent minus sign. If no explicit | ||
| 294 | * negative format is specified, one is formed by prefixing | ||
| 295 | * minusSign to the positive format. | ||
| 296 | */ | ||
| 297 | public char getMinusSign() { | ||
| 298 | return minusSign; | ||
| 299 | } | ||
| 300 | |||
| 301 | /** | ||
| 302 | * Sets the character used to represent minus sign. If no explicit | ||
| 303 | * negative format is specified, one is formed by prefixing | ||
| 304 | * minusSign to the positive format. | ||
| 305 | */ | ||
| 306 | public void setMinusSign(char minusSign) { | ||
| 307 | this.minusSign = minusSign; | ||
| 308 | } | ||
| 309 | |||
| 310 | /** | ||
| 311 | * Returns the currency symbol for the currency of these | ||
| 312 | * DecimalFormatSymbols in their locale. | ||
| 313 | * @since 1.2 | ||
| 314 | */ | ||
| 315 | public String getCurrencySymbol() | ||
| 316 | { | ||
| 317 | return currencySymbol; | ||
| 318 | } | ||
| 319 | |||
| 320 | /** | ||
| 321 | * Sets the currency symbol for the currency of these | ||
| 322 | * DecimalFormatSymbols in their locale. | ||
| 323 | * @since 1.2 | ||
| 324 | */ | ||
| 325 | public void setCurrencySymbol(String currency) | ||
| 326 | { | ||
| 327 | currencySymbol = currency; | ||
| 328 | } | ||
| 329 | |||
| 330 | /** | ||
| 331 | * Returns the ISO 4217 currency code of the currency of these | ||
| 332 | * DecimalFormatSymbols. | ||
| 333 | * @since 1.2 | ||
| 334 | */ | ||
| 335 | public String getInternationalCurrencySymbol() | ||
| 336 | { | ||
| 337 | return intlCurrencySymbol; | ||
| 338 | } | ||
| 339 | |||
| 340 | /** | ||
| 341 | * Sets the ISO 4217 currency code of the currency of these | ||
| 342 | * DecimalFormatSymbols. | ||
| 343 | * If the currency code is valid (as defined by | ||
| 344 | * {@link java.util.Currency#getInstance(java.lang.String) Currency.getInstance}), | ||
| 345 | * this also sets the currency attribute to the corresponding Currency | ||
| 346 | * instance and the currency symbol attribute to the currency's symbol | ||
| 347 | * in the DecimalFormatSymbols' locale. If the currency code is not valid, | ||
| 348 | * then the currency attribute is set to null and the currency symbol | ||
| 349 | * attribute is not modified. | ||
| 350 | * | ||
| 351 | * @see #setCurrency | ||
| 352 | * @see #setCurrencySymbol | ||
| 353 | * @since 1.2 | ||
| 354 | */ | ||
| 355 | public void setInternationalCurrencySymbol(String currencyCode) | ||
| 356 | { | ||
| 357 | intlCurrencySymbol = currencyCode; | ||
| 358 | currency = null; | ||
| 359 | if (currencyCode != null) { | ||
| 360 | try { | ||
| 361 | currency = Currency.getInstance(currencyCode); | ||
| 362 | currencySymbol = currency.getSymbol(); | ||
| 363 | } catch (IllegalArgumentException e) { | ||
| 364 | } | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 368 | /** | ||
| 369 | * Gets the currency of these DecimalFormatSymbols. May be null if the | ||
| 370 | * currency symbol attribute was previously set to a value that's not | ||
| 371 | * a valid ISO 4217 currency code. | ||
| 372 | * | ||
| 373 | * @return the currency used, or null | ||
| 374 | * @since 1.4 | ||
| 375 | */ | ||
| 376 | public Currency getCurrency() { | ||
| 377 | return currency; | ||
| 378 | } | ||
| 379 | |||
| 380 | /** | ||
| 381 | * Sets the currency of these DecimalFormatSymbols. | ||
| 382 | * This also sets the currency symbol attribute to the currency's symbol | ||
| 383 | * in the DecimalFormatSymbols' locale, and the international currency | ||
| 384 | * symbol attribute to the currency's ISO 4217 currency code. | ||
| 385 | * | ||
| 386 | * @param currency the new currency to be used | ||
| 387 | * @exception NullPointerException if <code>currency</code> is null | ||
| 388 | * @since 1.4 | ||
| 389 | * @see #setCurrencySymbol | ||
| 390 | * @see #setInternationalCurrencySymbol | ||
| 391 | */ | ||
| 392 | public void setCurrency(Currency currency) { | ||
| 393 | if (currency == null) { | ||
| 394 | throw new NullPointerException(); | ||
| 395 | } | ||
| 396 | this.currency = currency; | ||
| 397 | intlCurrencySymbol = currency.getCurrencyCode(); | ||
| 398 | currencySymbol = currency.getSymbol(locale); | ||
| 399 | } | ||
| 400 | |||
| 401 | |||
| 402 | /** | ||
| 403 | * Returns the monetary decimal separator. | ||
| 404 | * @since 1.2 | ||
| 405 | */ | ||
| 406 | public char getMonetaryDecimalSeparator() | ||
| 407 | { | ||
| 408 | return monetarySeparator; | ||
| 409 | } | ||
| 410 | |||
| 411 | /** | ||
| 412 | * Sets the monetary decimal separator. | ||
| 413 | * @since 1.2 | ||
| 414 | */ | ||
| 415 | public void setMonetaryDecimalSeparator(char sep) | ||
| 416 | { | ||
| 417 | monetarySeparator = sep; | ||
| 418 | } | ||
| 419 | |||
| 420 | //------------------------------------------------------------ | ||
| 421 | // BEGIN Package Private methods ... to be made public later | ||
| 422 | //------------------------------------------------------------ | ||
| 423 | |||
| 424 | /** | ||
| 425 | * Returns the character used to separate the mantissa from the exponent. | ||
| 426 | */ | ||
| 427 | char getExponentialSymbol() | ||
| 428 | { | ||
| 429 | return exponential; | ||
| 430 | } | ||
| 431 | /** | ||
| 432 | * Returns the string used to separate the mantissa from the exponent. | ||
| 433 | * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4. | ||
| 434 | * | ||
| 435 | * @return the exponent separator string | ||
| 436 | * @see #setExponentSeparator(java.lang.String) | ||
| 437 | * @since 1.6 | ||
| 438 | */ | ||
| 439 | public String getExponentSeparator() | ||
| 440 | { | ||
| 441 | return exponentialSeparator; | ||
| 442 | } | ||
| 443 | |||
| 444 | /** | ||
| 445 | * Sets the character used to separate the mantissa from the exponent. | ||
| 446 | */ | ||
| 447 | void setExponentialSymbol(char exp) | ||
| 448 | { | ||
| 449 | exponential = exp; | ||
| 450 | } | ||
| 451 | |||
| 452 | /** | ||
| 453 | * Sets the string used to separate the mantissa from the exponent. | ||
| 454 | * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4. | ||
| 455 | * | ||
| 456 | * @param exp the exponent separator string | ||
| 457 | * @exception NullPointerException if <code>exp</code> is null | ||
| 458 | * @see #getExponentSeparator() | ||
| 459 | * @since 1.6 | ||
| 460 | */ | ||
| 461 | public void setExponentSeparator(String exp) | ||
| 462 | { | ||
| 463 | if (exp == null) { | ||
| 464 | throw new NullPointerException(); | ||
| 465 | } | ||
| 466 | exponentialSeparator = exp; | ||
| 467 | } | ||
| 468 | |||
| 469 | |||
| 470 | //------------------------------------------------------------ | ||
| 471 | // END Package Private methods ... to be made public later | ||
| 472 | //------------------------------------------------------------ | ||
| 473 | |||
| 474 | /** | ||
| 475 | * Standard override. | ||
| 476 | */ | ||
| 477 | public Object clone() { | ||
| 478 | try { | ||
| 479 | return (DecimalFormatSymbols)super.clone(); | ||
| 480 | // other fields are bit-copied | ||
| 481 | } catch (CloneNotSupportedException e) { | ||
| 482 | throw new InternalError(); | ||
| 483 | } | ||
| 484 | } | ||
| 485 | |||
| 486 | /** | ||
| 487 | * Override equals. | ||
| 488 | */ | ||
| 489 | public boolean equals(Object obj) { | ||
| 490 | if (obj == null) return false; | ||
| 491 | if (this == obj) return true; | ||
| 492 | if (getClass() != obj.getClass()) return false; | ||
| 493 | DecimalFormatSymbols other = (DecimalFormatSymbols) obj; | ||
| 494 | return (zeroDigit == other.zeroDigit && | ||
| 495 | groupingSeparator == other.groupingSeparator && | ||
| 496 | decimalSeparator == other.decimalSeparator && | ||
| 497 | percent == other.percent && | ||
| 498 | perMill == other.perMill && | ||
| 499 | digit == other.digit && | ||
| 500 | minusSign == other.minusSign && | ||
| 501 | patternSeparator == other.patternSeparator && | ||
| 502 | infinity.equals(other.infinity) && | ||
| 503 | NaN.equals(other.NaN) && | ||
| 504 | currencySymbol.equals(other.currencySymbol) && | ||
| 505 | intlCurrencySymbol.equals(other.intlCurrencySymbol) && | ||
| 506 | currency == other.currency && | ||
| 507 | monetarySeparator == other.monetarySeparator && | ||
| 508 | exponentialSeparator.equals(other.exponentialSeparator) && | ||
| 509 | locale.equals(other.locale)); | ||
| 510 | } | ||
| 511 | |||
| 512 | /** | ||
| 513 | * Override hashCode. | ||
| 514 | */ | ||
| 515 | public int hashCode() { | ||
| 516 | int result = zeroDigit; | ||
| 517 | result = result * 37 + groupingSeparator; | ||
| 518 | result = result * 37 + decimalSeparator; | ||
| 519 | return result; | ||
| 520 | } | ||
| 521 | |||
| 522 | /** | ||
| 523 | * Initializes the symbols from the FormatData resource bundle. | ||
| 524 | */ | ||
| 525 | private void initialize( Locale locale ) { | ||
| 526 | this.locale = locale; | ||
| 527 | |||
| 528 | // get resource bundle data - try the cache first | ||
| 529 | boolean needCacheUpdate = false; | ||
| 530 | Object[] data = cachedLocaleData.get(locale); | ||
| 531 | if (data == null) { /* cache miss */ | ||
| 532 | // When numbering system is thai (Locale's extension contains u-nu-thai), | ||
| 533 | // we read the data from th_TH_TH. | ||
| 534 | Locale lookupLocale = locale; | ||
| 535 | String numberType = locale.getUnicodeLocaleType("nu"); | ||
| 536 | if (numberType != null && numberType.equals("thai")) { | ||
| 537 | lookupLocale = new Locale("th", "TH", "TH"); | ||
| 538 | } | ||
| 539 | data = new Object[3]; | ||
| 540 | ResourceBundle rb = LocaleData.getNumberFormatData(lookupLocale); | ||
| 541 | data[0] = rb.getStringArray("NumberElements"); | ||
| 542 | needCacheUpdate = true; | ||
| 543 | } | ||
| 544 | |||
| 545 | String[] numberElements = (String[]) data[0]; | ||
| 546 | |||
| 547 | decimalSeparator = numberElements[0].charAt(0); | ||
| 548 | groupingSeparator = numberElements[1].charAt(0); | ||
| 549 | patternSeparator = numberElements[2].charAt(0); | ||
| 550 | percent = numberElements[3].charAt(0); | ||
| 551 | zeroDigit = numberElements[4].charAt(0); //different for Arabic,etc. | ||
| 552 | digit = numberElements[5].charAt(0); | ||
| 553 | minusSign = numberElements[6].charAt(0); | ||
| 554 | exponential = numberElements[7].charAt(0); | ||
| 555 | exponentialSeparator = numberElements[7]; //string representation new since 1.6 | ||
| 556 | perMill = numberElements[8].charAt(0); | ||
| 557 | infinity = numberElements[9]; | ||
| 558 | NaN = numberElements[10]; | ||
| 559 | |||
| 560 | // Try to obtain the currency used in the locale's country. | ||
| 561 | // Check for empty country string separately because it's a valid | ||
| 562 | // country ID for Locale (and used for the C locale), but not a valid | ||
| 563 | // ISO 3166 country code, and exceptions are expensive. | ||
| 564 | if (!"".equals(locale.getCountry())) { | ||
| 565 | try { | ||
| 566 | currency = Currency.getInstance(locale); | ||
| 567 | } catch (IllegalArgumentException e) { | ||
| 568 | // use default values below for compatibility | ||
| 569 | } | ||
| 570 | } | ||
| 571 | if (currency != null) { | ||
| 572 | intlCurrencySymbol = currency.getCurrencyCode(); | ||
| 573 | if (data[1] != null && data[1] == intlCurrencySymbol) { | ||
| 574 | currencySymbol = (String) data[2]; | ||
| 575 | } else { | ||
| 576 | currencySymbol = currency.getSymbol(locale); | ||
| 577 | data[1] = intlCurrencySymbol; | ||
| 578 | data[2] = currencySymbol; | ||
| 579 | needCacheUpdate = true; | ||
| 580 | } | ||
| 581 | } else { | ||
| 582 | // default values | ||
| 583 | intlCurrencySymbol = "XXX"; | ||
| 584 | try { | ||
| 585 | currency = Currency.getInstance(intlCurrencySymbol); | ||
| 586 | } catch (IllegalArgumentException e) { | ||
| 587 | } | ||
| 588 | currencySymbol = "\u00A4"; | ||
| 589 | } | ||
| 590 | // Currently the monetary decimal separator is the same as the | ||
| 591 | // standard decimal separator for all locales that we support. | ||
| 592 | // If that changes, add a new entry to NumberElements. | ||
| 593 | monetarySeparator = decimalSeparator; | ||
| 594 | |||
| 595 | if (needCacheUpdate) { | ||
| 596 | cachedLocaleData.putIfAbsent(locale, data); | ||
| 597 | } | ||
| 598 | } | ||
| 599 | |||
| 600 | /** | ||
| 601 | * Reads the default serializable fields, provides default values for objects | ||
| 602 | * in older serial versions, and initializes non-serializable fields. | ||
| 603 | * If <code>serialVersionOnStream</code> | ||
| 604 | * is less than 1, initializes <code>monetarySeparator</code> to be | ||
| 605 | * the same as <code>decimalSeparator</code> and <code>exponential</code> | ||
| 606 | * to be 'E'. | ||
| 607 | * If <code>serialVersionOnStream</code> is less than 2, | ||
| 608 | * initializes <code>locale</code>to the root locale, and initializes | ||
| 609 | * If <code>serialVersionOnStream</code> is less than 3, it initializes | ||
| 610 | * <code>exponentialSeparator</code> using <code>exponential</code>. | ||
| 611 | * Sets <code>serialVersionOnStream</code> back to the maximum allowed value so that | ||
| 612 | * default serialization will work properly if this object is streamed out again. | ||
| 613 | * Initializes the currency from the intlCurrencySymbol field. | ||
| 614 | * | ||
| 615 | * @since JDK 1.1.6 | ||
| 616 | */ | ||
| 617 | private void readObject(ObjectInputStream stream) | ||
| 618 | throws IOException, ClassNotFoundException { | ||
| 619 | stream.defaultReadObject(); | ||
| 620 | if (serialVersionOnStream < 1) { | ||
| 621 | // Didn't have monetarySeparator or exponential field; | ||
| 622 | // use defaults. | ||
| 623 | monetarySeparator = decimalSeparator; | ||
| 624 | exponential = 'E'; | ||
| 625 | } | ||
| 626 | if (serialVersionOnStream < 2) { | ||
| 627 | // didn't have locale; use root locale | ||
| 628 | locale = Locale.ROOT; | ||
| 629 | } | ||
| 630 | if (serialVersionOnStream < 3) { | ||
| 631 | // didn't have exponentialSeparator. Create one using exponential | ||
| 632 | exponentialSeparator = Character.toString(exponential); | ||
| 633 | } | ||
| 634 | serialVersionOnStream = currentSerialVersion; | ||
| 635 | |||
| 636 | if (intlCurrencySymbol != null) { | ||
| 637 | try { | ||
| 638 | currency = Currency.getInstance(intlCurrencySymbol); | ||
| 639 | } catch (IllegalArgumentException e) { | ||
| 640 | } | ||
| 641 | } | ||
| 642 | } | ||
| 643 | |||
| 644 | /** | ||
| 645 | * Character used for zero. | ||
| 646 | * | ||
| 647 | * @serial | ||
| 648 | * @see #getZeroDigit | ||
| 649 | */ | ||
| 650 | private char zeroDigit; | ||
| 651 | |||
| 652 | /** | ||
| 653 | * Character used for thousands separator. | ||
| 654 | * | ||
| 655 | * @serial | ||
| 656 | * @see #getGroupingSeparator | ||
| 657 | */ | ||
| 658 | private char groupingSeparator; | ||
| 659 | |||
| 660 | /** | ||
| 661 | * Character used for decimal sign. | ||
| 662 | * | ||
| 663 | * @serial | ||
| 664 | * @see #getDecimalSeparator | ||
| 665 | */ | ||
| 666 | private char decimalSeparator; | ||
| 667 | |||
| 668 | /** | ||
| 669 | * Character used for per mille sign. | ||
| 670 | * | ||
| 671 | * @serial | ||
| 672 | * @see #getPerMill | ||
| 673 | */ | ||
| 674 | private char perMill; | ||
| 675 | |||
| 676 | /** | ||
| 677 | * Character used for percent sign. | ||
| 678 | * @serial | ||
| 679 | * @see #getPercent | ||
| 680 | */ | ||
| 681 | private char percent; | ||
| 682 | |||
| 683 | /** | ||
| 684 | * Character used for a digit in a pattern. | ||
| 685 | * | ||
| 686 | * @serial | ||
| 687 | * @see #getDigit | ||
| 688 | */ | ||
| 689 | private char digit; | ||
| 690 | |||
| 691 | /** | ||
| 692 | * Character used to separate positive and negative subpatterns | ||
| 693 | * in a pattern. | ||
| 694 | * | ||
| 695 | * @serial | ||
| 696 | * @see #getPatternSeparator | ||
| 697 | */ | ||
| 698 | private char patternSeparator; | ||
| 699 | |||
| 700 | /** | ||
| 701 | * String used to represent infinity. | ||
| 702 | * @serial | ||
| 703 | * @see #getInfinity | ||
| 704 | */ | ||
| 705 | private String infinity; | ||
| 706 | |||
| 707 | /** | ||
| 708 | * String used to represent "not a number". | ||
| 709 | * @serial | ||
| 710 | * @see #getNaN | ||
| 711 | */ | ||
| 712 | private String NaN; | ||
| 713 | |||
| 714 | /** | ||
| 715 | * Character used to represent minus sign. | ||
| 716 | * @serial | ||
| 717 | * @see #getMinusSign | ||
| 718 | */ | ||
| 719 | private char minusSign; | ||
| 720 | |||
| 721 | /** | ||
| 722 | * String denoting the local currency, e.g. "$". | ||
| 723 | * @serial | ||
| 724 | * @see #getCurrencySymbol | ||
| 725 | */ | ||
| 726 | private String currencySymbol; | ||
| 727 | |||
| 728 | /** | ||
| 729 | * ISO 4217 currency code denoting the local currency, e.g. "USD". | ||
| 730 | * @serial | ||
| 731 | * @see #getInternationalCurrencySymbol | ||
| 732 | */ | ||
| 733 | private String intlCurrencySymbol; | ||
| 734 | |||
| 735 | /** | ||
| 736 | * The decimal separator used when formatting currency values. | ||
| 737 | * @serial | ||
| 738 | * @since JDK 1.1.6 | ||
| 739 | * @see #getMonetaryDecimalSeparator | ||
| 740 | */ | ||
| 741 | private char monetarySeparator; // Field new in JDK 1.1.6 | ||
| 742 | |||
| 743 | /** | ||
| 744 | * The character used to distinguish the exponent in a number formatted | ||
| 745 | * in exponential notation, e.g. 'E' for a number such as "1.23E45". | ||
| 746 | * <p> | ||
| 747 | * Note that the public API provides no way to set this field, | ||
| 748 | * even though it is supported by the implementation and the stream format. | ||
| 749 | * The intent is that this will be added to the API in the future. | ||
| 750 | * | ||
| 751 | * @serial | ||
| 752 | * @since JDK 1.1.6 | ||
| 753 | */ | ||
| 754 | private char exponential; // Field new in JDK 1.1.6 | ||
| 755 | |||
| 756 | /** | ||
| 757 | * The string used to separate the mantissa from the exponent. | ||
| 758 | * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4. | ||
| 759 | * <p> | ||
| 760 | * If both <code>exponential</code> and <code>exponentialSeparator</code> | ||
| 761 | * exist, this <code>exponentialSeparator</code> has the precedence. | ||
| 762 | * | ||
| 763 | * @serial | ||
| 764 | * @since 1.6 | ||
| 765 | */ | ||
| 766 | private String exponentialSeparator; // Field new in JDK 1.6 | ||
| 767 | |||
| 768 | /** | ||
| 769 | * The locale of these currency format symbols. | ||
| 770 | * | ||
| 771 | * @serial | ||
| 772 | * @since 1.4 | ||
| 773 | */ | ||
| 774 | private Locale locale; | ||
| 775 | |||
| 776 | // currency; only the ISO code is serialized. | ||
| 777 | private transient Currency currency; | ||
| 778 | |||
| 779 | // Proclaim JDK 1.1 FCS compatibility | ||
| 780 | static final long serialVersionUID = 5772796243397350300L; | ||
| 781 | |||
| 782 | // The internal serial version which says which version was written | ||
| 783 | // - 0 (default) for version up to JDK 1.1.5 | ||
| 784 | // - 1 for version from JDK 1.1.6, which includes two new fields: | ||
| 785 | // monetarySeparator and exponential. | ||
| 786 | // - 2 for version from J2SE 1.4, which includes locale field. | ||
| 787 | // - 3 for version from J2SE 1.6, which includes exponentialSeparator field. | ||
| 788 | private static final int currentSerialVersion = 3; | ||
| 789 | |||
| 790 | /** | ||
| 791 | * Describes the version of <code>DecimalFormatSymbols</code> present on the stream. | ||
| 792 | * Possible values are: | ||
| 793 | * <ul> | ||
| 794 | * <li><b>0</b> (or uninitialized): versions prior to JDK 1.1.6. | ||
| 795 | * | ||
| 796 | * <li><b>1</b>: Versions written by JDK 1.1.6 or later, which include | ||
| 797 | * two new fields: <code>monetarySeparator</code> and <code>exponential</code>. | ||
| 798 | * <li><b>2</b>: Versions written by J2SE 1.4 or later, which include a | ||
| 799 | * new <code>locale</code> field. | ||
| 800 | * <li><b>3</b>: Versions written by J2SE 1.6 or later, which include a | ||
| 801 | * new <code>exponentialSeparator</code> field. | ||
| 802 | * </ul> | ||
| 803 | * When streaming out a <code>DecimalFormatSymbols</code>, the most recent format | ||
| 804 | * (corresponding to the highest allowable <code>serialVersionOnStream</code>) | ||
| 805 | * is always written. | ||
| 806 | * | ||
| 807 | * @serial | ||
| 808 | * @since JDK 1.1.6 | ||
| 809 | */ | ||
| 810 | private int serialVersionOnStream = currentSerialVersion; | ||
| 811 | |||
| 812 | /** | ||
| 813 | * cache to hold the NumberElements and the Currency | ||
| 814 | * of a Locale. | ||
| 815 | */ | ||
| 816 | private static final ConcurrentHashMap<Locale, Object[]> cachedLocaleData = new ConcurrentHashMap<Locale, Object[]>(3); | ||
| 817 | |||
| 818 | /** | ||
| 819 | * Obtains a DecimalFormatSymbols instance from a DecimalFormatSymbolsProvider | ||
| 820 | * implementation. | ||
| 821 | */ | ||
| 822 | private static class DecimalFormatSymbolsGetter | ||
| 823 | implements LocaleServiceProviderPool.LocalizedObjectGetter<DecimalFormatSymbolsProvider, | ||
| 824 | DecimalFormatSymbols> { | ||
| 825 | private static final DecimalFormatSymbolsGetter INSTANCE = | ||
| 826 | new DecimalFormatSymbolsGetter(); | ||
| 827 | |||
| 828 | public DecimalFormatSymbols getObject( | ||
| 829 | DecimalFormatSymbolsProvider decimalFormatSymbolsProvider, | ||
| 830 | Locale locale, | ||
| 831 | String key, | ||
| 832 | Object... params) { | ||
| 833 | assert params.length == 0; | ||
| 834 | return decimalFormatSymbolsProvider.getInstance(locale); | ||
| 835 | } | ||
| 836 | } | ||
| 837 | } | ||
Binary file not shown.
| @@ -0,0 +1,514 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * | ||
| 28 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 29 | * (C) Copyright IBM Corp. 1996 - 2002 - All Rights Reserved | ||
| 30 | * | ||
| 31 | * The original version of this source code and documentation | ||
| 32 | * is copyrighted and owned by Taligent, Inc., a wholly-owned | ||
| 33 | * subsidiary of IBM. These materials are provided under terms | ||
| 34 | * of a License Agreement between Taligent and Sun. This technology | ||
| 35 | * is protected by multiple US and International patents. | ||
| 36 | * | ||
| 37 | * This notice and attribution to Taligent may not be removed. | ||
| 38 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 39 | */ | ||
| 40 | |||
| 41 | package java.text; | ||
| 42 | |||
| 43 | import java.util.Vector; | ||
| 44 | import java.util.Stack; | ||
| 45 | import java.util.Hashtable; | ||
| 46 | import java.text.CharacterIterator; | ||
| 47 | import java.io.InputStream; | ||
| 48 | import java.io.IOException; | ||
| 49 | |||
| 50 | /** | ||
| 51 | * A subclass of RuleBasedBreakIterator that adds the ability to use a dictionary | ||
| 52 | * to further subdivide ranges of text beyond what is possible using just the | ||
| 53 | * state-table-based algorithm. This is necessary, for example, to handle | ||
| 54 | * word and line breaking in Thai, which doesn't use spaces between words. The | ||
| 55 | * state-table-based algorithm used by RuleBasedBreakIterator is used to divide | ||
| 56 | * up text as far as possible, and then contiguous ranges of letters are | ||
| 57 | * repeatedly compared against a list of known words (i.e., the dictionary) | ||
| 58 | * to divide them up into words. | ||
| 59 | * | ||
| 60 | * DictionaryBasedBreakIterator uses the same rule language as RuleBasedBreakIterator, | ||
| 61 | * but adds one more special substitution name: <dictionary>. This substitution | ||
| 62 | * name is used to identify characters in words in the dictionary. The idea is that | ||
| 63 | * if the iterator passes over a chunk of text that includes two or more characters | ||
| 64 | * in a row that are included in <dictionary>, it goes back through that range and | ||
| 65 | * derives additional break positions (if possible) using the dictionary. | ||
| 66 | * | ||
| 67 | * DictionaryBasedBreakIterator is also constructed with the filename of a dictionary | ||
| 68 | * file. It follows a prescribed search path to locate the dictionary (right now, | ||
| 69 | * it looks for it in /com/ibm/text/resources in each directory in the classpath, | ||
| 70 | * and won't find it in JAR files, but this location is likely to change). The | ||
| 71 | * dictionary file is in a serialized binary format. We have a very primitive (and | ||
| 72 | * slow) BuildDictionaryFile utility for creating dictionary files, but aren't | ||
| 73 | * currently making it public. Contact us for help. | ||
| 74 | */ | ||
| 75 | class DictionaryBasedBreakIterator extends RuleBasedBreakIterator { | ||
| 76 | |||
| 77 | /** | ||
| 78 | * a list of known words that is used to divide up contiguous ranges of letters, | ||
| 79 | * stored in a compressed, indexed, format that offers fast access | ||
| 80 | */ | ||
| 81 | private BreakDictionary dictionary; | ||
| 82 | |||
| 83 | /** | ||
| 84 | * a list of flags indicating which character categories are contained in | ||
| 85 | * the dictionary file (this is used to determine which ranges of characters | ||
| 86 | * to apply the dictionary to) | ||
| 87 | */ | ||
| 88 | private boolean[] categoryFlags; | ||
| 89 | |||
| 90 | /** | ||
| 91 | * a temporary hiding place for the number of dictionary characters in the | ||
| 92 | * last range passed over by next() | ||
| 93 | */ | ||
| 94 | private int dictionaryCharCount; | ||
| 95 | |||
| 96 | /** | ||
| 97 | * when a range of characters is divided up using the dictionary, the break | ||
| 98 | * positions that are discovered are stored here, preventing us from having | ||
| 99 | * to use either the dictionary or the state table again until the iterator | ||
| 100 | * leaves this range of text | ||
| 101 | */ | ||
| 102 | private int[] cachedBreakPositions; | ||
| 103 | |||
| 104 | /** | ||
| 105 | * if cachedBreakPositions is not null, this indicates which item in the | ||
| 106 | * cache the current iteration position refers to | ||
| 107 | */ | ||
| 108 | private int positionInCache; | ||
| 109 | |||
| 110 | /** | ||
| 111 | * Constructs a DictionaryBasedBreakIterator. | ||
| 112 | * @param description Same as the description parameter on RuleBasedBreakIterator, | ||
| 113 | * except for the special meaning of "<dictionary>". This parameter is just | ||
| 114 | * passed through to RuleBasedBreakIterator's constructor. | ||
| 115 | * @param dictionaryFilename The filename of the dictionary file to use | ||
| 116 | */ | ||
| 117 | public DictionaryBasedBreakIterator(String dataFile, String dictionaryFile) | ||
| 118 | throws IOException { | ||
| 119 | super(dataFile); | ||
| 120 | byte[] tmp = super.getAdditionalData(); | ||
| 121 | if (tmp != null) { | ||
| 122 | prepareCategoryFlags(tmp); | ||
| 123 | super.setAdditionalData(null); | ||
| 124 | } | ||
| 125 | dictionary = new BreakDictionary(dictionaryFile); | ||
| 126 | } | ||
| 127 | |||
| 128 | private void prepareCategoryFlags(byte[] data) { | ||
| 129 | categoryFlags = new boolean[data.length]; | ||
| 130 | for (int i = 0; i < data.length; i++) { | ||
| 131 | categoryFlags[i] = (data[i] == (byte)1) ? true : false; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | public void setText(CharacterIterator newText) { | ||
| 136 | super.setText(newText); | ||
| 137 | cachedBreakPositions = null; | ||
| 138 | dictionaryCharCount = 0; | ||
| 139 | positionInCache = 0; | ||
| 140 | } | ||
| 141 | |||
| 142 | /** | ||
| 143 | * Sets the current iteration position to the beginning of the text. | ||
| 144 | * (i.e., the CharacterIterator's starting offset). | ||
| 145 | * @return The offset of the beginning of the text. | ||
| 146 | */ | ||
| 147 | public int first() { | ||
| 148 | cachedBreakPositions = null; | ||
| 149 | dictionaryCharCount = 0; | ||
| 150 | positionInCache = 0; | ||
| 151 | return super.first(); | ||
| 152 | } | ||
| 153 | |||
| 154 | /** | ||
| 155 | * Sets the current iteration position to the end of the text. | ||
| 156 | * (i.e., the CharacterIterator's ending offset). | ||
| 157 | * @return The text's past-the-end offset. | ||
| 158 | */ | ||
| 159 | public int last() { | ||
| 160 | cachedBreakPositions = null; | ||
| 161 | dictionaryCharCount = 0; | ||
| 162 | positionInCache = 0; | ||
| 163 | return super.last(); | ||
| 164 | } | ||
| 165 | |||
| 166 | /** | ||
| 167 | * Advances the iterator one step backwards. | ||
| 168 | * @return The position of the last boundary position before the | ||
| 169 | * current iteration position | ||
| 170 | */ | ||
| 171 | public int previous() { | ||
| 172 | CharacterIterator text = getText(); | ||
| 173 | |||
| 174 | // if we have cached break positions and we're still in the range | ||
| 175 | // covered by them, just move one step backward in the cache | ||
| 176 | if (cachedBreakPositions != null && positionInCache > 0) { | ||
| 177 | --positionInCache; | ||
| 178 | text.setIndex(cachedBreakPositions[positionInCache]); | ||
| 179 | return cachedBreakPositions[positionInCache]; | ||
| 180 | } | ||
| 181 | |||
| 182 | // otherwise, dump the cache and use the inherited previous() method to move | ||
| 183 | // backward. This may fill up the cache with new break positions, in which | ||
| 184 | // case we have to mark our position in the cache | ||
| 185 | else { | ||
| 186 | cachedBreakPositions = null; | ||
| 187 | int result = super.previous(); | ||
| 188 | if (cachedBreakPositions != null) { | ||
| 189 | positionInCache = cachedBreakPositions.length - 2; | ||
| 190 | } | ||
| 191 | return result; | ||
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 | /** | ||
| 196 | * Sets the current iteration position to the last boundary position | ||
| 197 | * before the specified position. | ||
| 198 | * @param offset The position to begin searching from | ||
| 199 | * @return The position of the last boundary before "offset" | ||
| 200 | */ | ||
| 201 | public int preceding(int offset) { | ||
| 202 | CharacterIterator text = getText(); | ||
| 203 | checkOffset(offset, text); | ||
| 204 | |||
| 205 | // if we have no cached break positions, or "offset" is outside the | ||
| 206 | // range covered by the cache, we can just call the inherited routine | ||
| 207 | // (which will eventually call other routines in this class that may | ||
| 208 | // refresh the cache) | ||
| 209 | if (cachedBreakPositions == null || offset <= cachedBreakPositions[0] || | ||
| 210 | offset > cachedBreakPositions[cachedBreakPositions.length - 1]) { | ||
| 211 | cachedBreakPositions = null; | ||
| 212 | return super.preceding(offset); | ||
| 213 | } | ||
| 214 | |||
| 215 | // on the other hand, if "offset" is within the range covered by the cache, | ||
| 216 | // then all we have to do is search the cache for the last break position | ||
| 217 | // before "offset" | ||
| 218 | else { | ||
| 219 | positionInCache = 0; | ||
| 220 | while (positionInCache < cachedBreakPositions.length | ||
| 221 | && offset > cachedBreakPositions[positionInCache]) { | ||
| 222 | ++positionInCache; | ||
| 223 | } | ||
| 224 | --positionInCache; | ||
| 225 | text.setIndex(cachedBreakPositions[positionInCache]); | ||
| 226 | return text.getIndex(); | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | /** | ||
| 231 | * Sets the current iteration position to the first boundary position after | ||
| 232 | * the specified position. | ||
| 233 | * @param offset The position to begin searching forward from | ||
| 234 | * @return The position of the first boundary after "offset" | ||
| 235 | */ | ||
| 236 | public int following(int offset) { | ||
| 237 | CharacterIterator text = getText(); | ||
| 238 | checkOffset(offset, text); | ||
| 239 | |||
| 240 | // if we have no cached break positions, or if "offset" is outside the | ||
| 241 | // range covered by the cache, then dump the cache and call our | ||
| 242 | // inherited following() method. This will call other methods in this | ||
| 243 | // class that may refresh the cache. | ||
| 244 | if (cachedBreakPositions == null || offset < cachedBreakPositions[0] || | ||
| 245 | offset >= cachedBreakPositions[cachedBreakPositions.length - 1]) { | ||
| 246 | cachedBreakPositions = null; | ||
| 247 | return super.following(offset); | ||
| 248 | } | ||
| 249 | |||
| 250 | // on the other hand, if "offset" is within the range covered by the | ||
| 251 | // cache, then just search the cache for the first break position | ||
| 252 | // after "offset" | ||
| 253 | else { | ||
| 254 | positionInCache = 0; | ||
| 255 | while (positionInCache < cachedBreakPositions.length | ||
| 256 | && offset >= cachedBreakPositions[positionInCache]) { | ||
| 257 | ++positionInCache; | ||
| 258 | } | ||
| 259 | text.setIndex(cachedBreakPositions[positionInCache]); | ||
| 260 | return text.getIndex(); | ||
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 264 | /** | ||
| 265 | * This is the implementation function for next(). | ||
| 266 | */ | ||
| 267 | protected int handleNext() { | ||
| 268 | CharacterIterator text = getText(); | ||
| 269 | |||
| 270 | // if there are no cached break positions, or if we've just moved | ||
| 271 | // off the end of the range covered by the cache, we have to dump | ||
| 272 | // and possibly regenerate the cache | ||
| 273 | if (cachedBreakPositions == null || | ||
| 274 | positionInCache == cachedBreakPositions.length - 1) { | ||
| 275 | |||
| 276 | // start by using the inherited handleNext() to find a tentative return | ||
| 277 | // value. dictionaryCharCount tells us how many dictionary characters | ||
| 278 | // we passed over on our way to the tentative return value | ||
| 279 | int startPos = text.getIndex(); | ||
| 280 | dictionaryCharCount = 0; | ||
| 281 | int result = super.handleNext(); | ||
| 282 | |||
| 283 | // if we passed over more than one dictionary character, then we use | ||
| 284 | // divideUpDictionaryRange() to regenerate the cached break positions | ||
| 285 | // for the new range | ||
| 286 | if (dictionaryCharCount > 1 && result - startPos > 1) { | ||
| 287 | divideUpDictionaryRange(startPos, result); | ||
| 288 | } | ||
| 289 | |||
| 290 | // otherwise, the value we got back from the inherited fuction | ||
| 291 | // is our return value, and we can dump the cache | ||
| 292 | else { | ||
| 293 | cachedBreakPositions = null; | ||
| 294 | return result; | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | // if the cache of break positions has been regenerated (or existed all | ||
| 299 | // along), then just advance to the next break position in the cache | ||
| 300 | // and return it | ||
| 301 | if (cachedBreakPositions != null) { | ||
| 302 | ++positionInCache; | ||
| 303 | text.setIndex(cachedBreakPositions[positionInCache]); | ||
| 304 | return cachedBreakPositions[positionInCache]; | ||
| 305 | } | ||
| 306 | return -9999; // SHOULD NEVER GET HERE! | ||
| 307 | } | ||
| 308 | |||
| 309 | /** | ||
| 310 | * Looks up a character category for a character. | ||
| 311 | */ | ||
| 312 | protected int lookupCategory(int c) { | ||
| 313 | // this override of lookupCategory() exists only to keep track of whether we've | ||
| 314 | // passed over any dictionary characters. It calls the inherited lookupCategory() | ||
| 315 | // to do the real work, and then checks whether its return value is one of the | ||
| 316 | // categories represented in the dictionary. If it is, bump the dictionary- | ||
| 317 | // character count. | ||
| 318 | int result = super.lookupCategory(c); | ||
| 319 | if (result != RuleBasedBreakIterator.IGNORE && categoryFlags[result]) { | ||
| 320 | ++dictionaryCharCount; | ||
| 321 | } | ||
| 322 | return result; | ||
| 323 | } | ||
| 324 | |||
| 325 | /** | ||
| 326 | * This is the function that actually implements the dictionary-based | ||
| 327 | * algorithm. Given the endpoints of a range of text, it uses the | ||
| 328 | * dictionary to determine the positions of any boundaries in this | ||
| 329 | * range. It stores all the boundary positions it discovers in | ||
| 330 | * cachedBreakPositions so that we only have to do this work once | ||
| 331 | * for each time we enter the range. | ||
| 332 | */ | ||
| 333 | private void divideUpDictionaryRange(int startPos, int endPos) { | ||
| 334 | CharacterIterator text = getText(); | ||
| 335 | |||
| 336 | // the range we're dividing may begin or end with non-dictionary characters | ||
| 337 | // (i.e., for line breaking, we may have leading or trailing punctuation | ||
| 338 | // that needs to be kept with the word). Seek from the beginning of the | ||
| 339 | // range to the first dictionary character | ||
| 340 | text.setIndex(startPos); | ||
| 341 | int c = getCurrent(); | ||
| 342 | int category = lookupCategory(c); | ||
| 343 | while (category == IGNORE || !categoryFlags[category]) { | ||
| 344 | c = getNext(); | ||
| 345 | category = lookupCategory(c); | ||
| 346 | } | ||
| 347 | |||
| 348 | // initialize. We maintain two stacks: currentBreakPositions contains | ||
| 349 | // the list of break positions that will be returned if we successfully | ||
| 350 | // finish traversing the whole range now. possibleBreakPositions lists | ||
| 351 | // all other possible word ends we've passed along the way. (Whenever | ||
| 352 | // we reach an error [a sequence of characters that can't begin any word | ||
| 353 | // in the dictionary], we back up, possibly delete some breaks from | ||
| 354 | // currentBreakPositions, move a break from possibleBreakPositions | ||
| 355 | // to currentBreakPositions, and start over from there. This process | ||
| 356 | // continues in this way until we either successfully make it all the way | ||
| 357 | // across the range, or exhaust all of our combinations of break | ||
| 358 | // positions.) | ||
| 359 | Stack currentBreakPositions = new Stack(); | ||
| 360 | Stack possibleBreakPositions = new Stack(); | ||
| 361 | Vector wrongBreakPositions = new Vector(); | ||
| 362 | |||
| 363 | // the dictionary is implemented as a trie, which is treated as a state | ||
| 364 | // machine. -1 represents the end of a legal word. Every word in the | ||
| 365 | // dictionary is represented by a path from the root node to -1. A path | ||
| 366 | // that ends in state 0 is an illegal combination of characters. | ||
| 367 | int state = 0; | ||
| 368 | |||
| 369 | // these two variables are used for error handling. We keep track of the | ||
| 370 | // farthest we've gotten through the range being divided, and the combination | ||
| 371 | // of breaks that got us that far. If we use up all possible break | ||
| 372 | // combinations, the text contains an error or a word that's not in the | ||
| 373 | // dictionary. In this case, we "bless" the break positions that got us the | ||
| 374 | // farthest as real break positions, and then start over from scratch with | ||
| 375 | // the character where the error occurred. | ||
| 376 | int farthestEndPoint = text.getIndex(); | ||
| 377 | Stack bestBreakPositions = null; | ||
| 378 | |||
| 379 | // initialize (we always exit the loop with a break statement) | ||
| 380 | c = getCurrent(); | ||
| 381 | while (true) { | ||
| 382 | |||
| 383 | // if we can transition to state "-1" from our current state, we're | ||
| 384 | // on the last character of a legal word. Push that position onto | ||
| 385 | // the possible-break-positions stack | ||
| 386 | if (dictionary.getNextState(state, 0) == -1) { | ||
| 387 | possibleBreakPositions.push(Integer.valueOf(text.getIndex())); | ||
| 388 | } | ||
| 389 | |||
| 390 | // look up the new state to transition to in the dictionary | ||
| 391 | state = dictionary.getNextStateFromCharacter(state, c); | ||
| 392 | |||
| 393 | // if the character we're sitting on causes us to transition to | ||
| 394 | // the "end of word" state, then it was a non-dictionary character | ||
| 395 | // and we've successfully traversed the whole range. Drop out | ||
| 396 | // of the loop. | ||
| 397 | if (state == -1) { | ||
| 398 | currentBreakPositions.push(Integer.valueOf(text.getIndex())); | ||
| 399 | break; | ||
| 400 | } | ||
| 401 | |||
| 402 | // if the character we're sitting on causes us to transition to | ||
| 403 | // the error state, or if we've gone off the end of the range | ||
| 404 | // without transitioning to the "end of word" state, we've hit | ||
| 405 | // an error... | ||
| 406 | else if (state == 0 || text.getIndex() >= endPos) { | ||
| 407 | |||
| 408 | // if this is the farthest we've gotten, take note of it in | ||
| 409 | // case there's an error in the text | ||
| 410 | if (text.getIndex() > farthestEndPoint) { | ||
| 411 | farthestEndPoint = text.getIndex(); | ||
| 412 | bestBreakPositions = (Stack)(currentBreakPositions.clone()); | ||
| 413 | } | ||
| 414 | |||
| 415 | // wrongBreakPositions is a list of all break positions | ||
| 416 | // we've tried starting that didn't allow us to traverse | ||
| 417 | // all the way through the text. Every time we pop a | ||
| 418 | //break position off of currentBreakPositions, we put it | ||
| 419 | // into wrongBreakPositions to avoid trying it again later. | ||
| 420 | // If we make it to this spot, we're either going to back | ||
| 421 | // up to a break in possibleBreakPositions and try starting | ||
| 422 | // over from there, or we've exhausted all possible break | ||
| 423 | // positions and are going to do the fallback procedure. | ||
| 424 | // This loop prevents us from messing with anything in | ||
| 425 | // possibleBreakPositions that didn't work as a starting | ||
| 426 | // point the last time we tried it (this is to prevent a bunch of | ||
| 427 | // repetitive checks from slowing down some extreme cases) | ||
| 428 | Integer newStartingSpot = null; | ||
| 429 | while (!possibleBreakPositions.isEmpty() && wrongBreakPositions.contains( | ||
| 430 | possibleBreakPositions.peek())) { | ||
| 431 | possibleBreakPositions.pop(); | ||
| 432 | } | ||
| 433 | |||
| 434 | // if we've used up all possible break-position combinations, there's | ||
| 435 | // an error or an unknown word in the text. In this case, we start | ||
| 436 | // over, treating the farthest character we've reached as the beginning | ||
| 437 | // of the range, and "blessing" the break positions that got us that | ||
| 438 | // far as real break positions | ||
| 439 | if (possibleBreakPositions.isEmpty()) { | ||
| 440 | if (bestBreakPositions != null) { | ||
| 441 | currentBreakPositions = bestBreakPositions; | ||
| 442 | if (farthestEndPoint < endPos) { | ||
| 443 | text.setIndex(farthestEndPoint + 1); | ||
| 444 | } | ||
| 445 | else { | ||
| 446 | break; | ||
| 447 | } | ||
| 448 | } | ||
| 449 | else { | ||
| 450 | if ((currentBreakPositions.size() == 0 || | ||
| 451 | ((Integer)(currentBreakPositions.peek())).intValue() != text.getIndex()) | ||
| 452 | && text.getIndex() != startPos) { | ||
| 453 | currentBreakPositions.push(new Integer(text.getIndex())); | ||
| 454 | } | ||
| 455 | getNext(); | ||
| 456 | currentBreakPositions.push(new Integer(text.getIndex())); | ||
| 457 | } | ||
| 458 | } | ||
| 459 | |||
| 460 | // if we still have more break positions we can try, then promote the | ||
| 461 | // last break in possibleBreakPositions into currentBreakPositions, | ||
| 462 | // and get rid of all entries in currentBreakPositions that come after | ||
| 463 | // it. Then back up to that position and start over from there (i.e., | ||
| 464 | // treat that position as the beginning of a new word) | ||
| 465 | else { | ||
| 466 | Integer temp = (Integer)possibleBreakPositions.pop(); | ||
| 467 | Object temp2 = null; | ||
| 468 | while (!currentBreakPositions.isEmpty() && temp.intValue() < | ||
| 469 | ((Integer)currentBreakPositions.peek()).intValue()) { | ||
| 470 | temp2 = currentBreakPositions.pop(); | ||
| 471 | wrongBreakPositions.addElement(temp2); | ||
| 472 | } | ||
| 473 | currentBreakPositions.push(temp); | ||
| 474 | text.setIndex(((Integer)currentBreakPositions.peek()).intValue()); | ||
| 475 | } | ||
| 476 | |||
| 477 | // re-sync "c" for the next go-round, and drop out of the loop if | ||
| 478 | // we've made it off the end of the range | ||
| 479 | c = getCurrent(); | ||
| 480 | if (text.getIndex() >= endPos) { | ||
| 481 | break; | ||
| 482 | } | ||
| 483 | } | ||
| 484 | |||
| 485 | // if we didn't hit any exceptional conditions on this last iteration, | ||
| 486 | // just advance to the next character and loop | ||
| 487 | else { | ||
| 488 | c = getNext(); | ||
| 489 | } | ||
| 490 | } | ||
| 491 | |||
| 492 | // dump the last break position in the list, and replace it with the actual | ||
| 493 | // end of the range (which may be the same character, or may be further on | ||
| 494 | // because the range actually ended with non-dictionary characters we want to | ||
| 495 | // keep with the word) | ||
| 496 | if (!currentBreakPositions.isEmpty()) { | ||
| 497 | currentBreakPositions.pop(); | ||
| 498 | } | ||
| 499 | currentBreakPositions.push(Integer.valueOf(endPos)); | ||
| 500 | |||
| 501 | // create a regular array to hold the break positions and copy | ||
| 502 | // the break positions from the stack to the array (in addition, | ||
| 503 | // our starting position goes into this array as a break position). | ||
| 504 | // This array becomes the cache of break positions used by next() | ||
| 505 | // and previous(), so this is where we actually refresh the cache. | ||
| 506 | cachedBreakPositions = new int[currentBreakPositions.size() + 1]; | ||
| 507 | cachedBreakPositions[0] = startPos; | ||
| 508 | |||
| 509 | for (int i = 0; i < currentBreakPositions.size(); i++) { | ||
| 510 | cachedBreakPositions[i + 1] = ((Integer)currentBreakPositions.elementAt(i)).intValue(); | ||
| 511 | } | ||
| 512 | positionInCache = 0; | ||
| 513 | } | ||
| 514 | } | ||
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,715 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.math.BigDecimal; | ||
| 42 | import java.math.BigInteger; | ||
| 43 | import java.math.RoundingMode; | ||
| 44 | |||
| 45 | /** | ||
| 46 | * Digit List. Private to DecimalFormat. | ||
| 47 | * Handles the transcoding | ||
| 48 | * between numeric values and strings of characters. Only handles | ||
| 49 | * non-negative numbers. The division of labor between DigitList and | ||
| 50 | * DecimalFormat is that DigitList handles the radix 10 representation | ||
| 51 | * issues; DecimalFormat handles the locale-specific issues such as | ||
| 52 | * positive/negative, grouping, decimal point, currency, and so on. | ||
| 53 | * | ||
| 54 | * A DigitList is really a representation of a floating point value. | ||
| 55 | * It may be an integer value; we assume that a double has sufficient | ||
| 56 | * precision to represent all digits of a long. | ||
| 57 | * | ||
| 58 | * The DigitList representation consists of a string of characters, | ||
| 59 | * which are the digits radix 10, from '0' to '9'. It also has a radix | ||
| 60 | * 10 exponent associated with it. The value represented by a DigitList | ||
| 61 | * object can be computed by mulitplying the fraction f, where 0 <= f < 1, | ||
| 62 | * derived by placing all the digits of the list to the right of the | ||
| 63 | * decimal point, by 10^exponent. | ||
| 64 | * | ||
| 65 | * @see Locale | ||
| 66 | * @see Format | ||
| 67 | * @see NumberFormat | ||
| 68 | * @see DecimalFormat | ||
| 69 | * @see ChoiceFormat | ||
| 70 | * @see MessageFormat | ||
| 71 | * @author Mark Davis, Alan Liu | ||
| 72 | */ | ||
| 73 | final class DigitList implements Cloneable { | ||
| 74 | /** | ||
| 75 | * The maximum number of significant digits in an IEEE 754 double, that | ||
| 76 | * is, in a Java double. This must not be increased, or garbage digits | ||
| 77 | * will be generated, and should not be decreased, or accuracy will be lost. | ||
| 78 | */ | ||
| 79 | public static final int MAX_COUNT = 19; // == Long.toString(Long.MAX_VALUE).length() | ||
| 80 | |||
| 81 | /** | ||
| 82 | * These data members are intentionally public and can be set directly. | ||
| 83 | * | ||
| 84 | * The value represented is given by placing the decimal point before | ||
| 85 | * digits[decimalAt]. If decimalAt is < 0, then leading zeros between | ||
| 86 | * the decimal point and the first nonzero digit are implied. If decimalAt | ||
| 87 | * is > count, then trailing zeros between the digits[count-1] and the | ||
| 88 | * decimal point are implied. | ||
| 89 | * | ||
| 90 | * Equivalently, the represented value is given by f * 10^decimalAt. Here | ||
| 91 | * f is a value 0.1 <= f < 1 arrived at by placing the digits in Digits to | ||
| 92 | * the right of the decimal. | ||
| 93 | * | ||
| 94 | * DigitList is normalized, so if it is non-zero, figits[0] is non-zero. We | ||
| 95 | * don't allow denormalized numbers because our exponent is effectively of | ||
| 96 | * unlimited magnitude. The count value contains the number of significant | ||
| 97 | * digits present in digits[]. | ||
| 98 | * | ||
| 99 | * Zero is represented by any DigitList with count == 0 or with each digits[i] | ||
| 100 | * for all i <= count == '0'. | ||
| 101 | */ | ||
| 102 | public int decimalAt = 0; | ||
| 103 | public int count = 0; | ||
| 104 | public char[] digits = new char[MAX_COUNT]; | ||
| 105 | |||
| 106 | private char[] data; | ||
| 107 | private RoundingMode roundingMode = RoundingMode.HALF_EVEN; | ||
| 108 | private boolean isNegative = false; | ||
| 109 | |||
| 110 | /** | ||
| 111 | * Return true if the represented number is zero. | ||
| 112 | */ | ||
| 113 | boolean isZero() { | ||
| 114 | for (int i=0; i < count; ++i) { | ||
| 115 | if (digits[i] != '0') { | ||
| 116 | return false; | ||
| 117 | } | ||
| 118 | } | ||
| 119 | return true; | ||
| 120 | } | ||
| 121 | |||
| 122 | /** | ||
| 123 | * Set the rounding mode | ||
| 124 | */ | ||
| 125 | void setRoundingMode(RoundingMode r) { | ||
| 126 | roundingMode = r; | ||
| 127 | } | ||
| 128 | |||
| 129 | /** | ||
| 130 | * Clears out the digits. | ||
| 131 | * Use before appending them. | ||
| 132 | * Typically, you set a series of digits with append, then at the point | ||
| 133 | * you hit the decimal point, you set myDigitList.decimalAt = myDigitList.count; | ||
| 134 | * then go on appending digits. | ||
| 135 | */ | ||
| 136 | public void clear () { | ||
| 137 | decimalAt = 0; | ||
| 138 | count = 0; | ||
| 139 | } | ||
| 140 | |||
| 141 | /** | ||
| 142 | * Appends a digit to the list, extending the list when necessary. | ||
| 143 | */ | ||
| 144 | public void append(char digit) { | ||
| 145 | if (count == digits.length) { | ||
| 146 | char[] data = new char[count + 100]; | ||
| 147 | System.arraycopy(digits, 0, data, 0, count); | ||
| 148 | digits = data; | ||
| 149 | } | ||
| 150 | digits[count++] = digit; | ||
| 151 | } | ||
| 152 | |||
| 153 | /** | ||
| 154 | * Utility routine to get the value of the digit list | ||
| 155 | * If (count == 0) this throws a NumberFormatException, which | ||
| 156 | * mimics Long.parseLong(). | ||
| 157 | */ | ||
| 158 | public final double getDouble() { | ||
| 159 | if (count == 0) { | ||
| 160 | return 0.0; | ||
| 161 | } | ||
| 162 | |||
| 163 | StringBuffer temp = getStringBuffer(); | ||
| 164 | temp.append('.'); | ||
| 165 | temp.append(digits, 0, count); | ||
| 166 | temp.append('E'); | ||
| 167 | temp.append(decimalAt); | ||
| 168 | return Double.parseDouble(temp.toString()); | ||
| 169 | } | ||
| 170 | |||
| 171 | /** | ||
| 172 | * Utility routine to get the value of the digit list. | ||
| 173 | * If (count == 0) this returns 0, unlike Long.parseLong(). | ||
| 174 | */ | ||
| 175 | public final long getLong() { | ||
| 176 | // for now, simple implementation; later, do proper IEEE native stuff | ||
| 177 | |||
| 178 | if (count == 0) { | ||
| 179 | return 0; | ||
| 180 | } | ||
| 181 | |||
| 182 | // We have to check for this, because this is the one NEGATIVE value | ||
| 183 | // we represent. If we tried to just pass the digits off to parseLong, | ||
| 184 | // we'd get a parse failure. | ||
| 185 | if (isLongMIN_VALUE()) { | ||
| 186 | return Long.MIN_VALUE; | ||
| 187 | } | ||
| 188 | |||
| 189 | StringBuffer temp = getStringBuffer(); | ||
| 190 | temp.append(digits, 0, count); | ||
| 191 | for (int i = count; i < decimalAt; ++i) { | ||
| 192 | temp.append('0'); | ||
| 193 | } | ||
| 194 | return Long.parseLong(temp.toString()); | ||
| 195 | } | ||
| 196 | |||
| 197 | public final BigDecimal getBigDecimal() { | ||
| 198 | if (count == 0) { | ||
| 199 | if (decimalAt == 0) { | ||
| 200 | return BigDecimal.ZERO; | ||
| 201 | } else { | ||
| 202 | return new BigDecimal("0E" + decimalAt); | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | if (decimalAt == count) { | ||
| 207 | return new BigDecimal(digits, 0, count); | ||
| 208 | } else { | ||
| 209 | return new BigDecimal(digits, 0, count).scaleByPowerOfTen(decimalAt - count); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | /** | ||
| 214 | * Return true if the number represented by this object can fit into | ||
| 215 | * a long. | ||
| 216 | * @param isPositive true if this number should be regarded as positive | ||
| 217 | * @param ignoreNegativeZero true if -0 should be regarded as identical to | ||
| 218 | * +0; otherwise they are considered distinct | ||
| 219 | * @return true if this number fits into a Java long | ||
| 220 | */ | ||
| 221 | boolean fitsIntoLong(boolean isPositive, boolean ignoreNegativeZero) { | ||
| 222 | // Figure out if the result will fit in a long. We have to | ||
| 223 | // first look for nonzero digits after the decimal point; | ||
| 224 | // then check the size. If the digit count is 18 or less, then | ||
| 225 | // the value can definitely be represented as a long. If it is 19 | ||
| 226 | // then it may be too large. | ||
| 227 | |||
| 228 | // Trim trailing zeros. This does not change the represented value. | ||
| 229 | while (count > 0 && digits[count - 1] == '0') { | ||
| 230 | --count; | ||
| 231 | } | ||
| 232 | |||
| 233 | if (count == 0) { | ||
| 234 | // Positive zero fits into a long, but negative zero can only | ||
| 235 | // be represented as a double. - bug 4162852 | ||
| 236 | return isPositive || ignoreNegativeZero; | ||
| 237 | } | ||
| 238 | |||
| 239 | if (decimalAt < count || decimalAt > MAX_COUNT) { | ||
| 240 | return false; | ||
| 241 | } | ||
| 242 | |||
| 243 | if (decimalAt < MAX_COUNT) return true; | ||
| 244 | |||
| 245 | // At this point we have decimalAt == count, and count == MAX_COUNT. | ||
| 246 | // The number will overflow if it is larger than 9223372036854775807 | ||
| 247 | // or smaller than -9223372036854775808. | ||
| 248 | for (int i=0; i<count; ++i) { | ||
| 249 | char dig = digits[i], max = LONG_MIN_REP[i]; | ||
| 250 | if (dig > max) return false; | ||
| 251 | if (dig < max) return true; | ||
| 252 | } | ||
| 253 | |||
| 254 | // At this point the first count digits match. If decimalAt is less | ||
| 255 | // than count, then the remaining digits are zero, and we return true. | ||
| 256 | if (count < decimalAt) return true; | ||
| 257 | |||
| 258 | // Now we have a representation of Long.MIN_VALUE, without the leading | ||
| 259 | // negative sign. If this represents a positive value, then it does | ||
| 260 | // not fit; otherwise it fits. | ||
| 261 | return !isPositive; | ||
| 262 | } | ||
| 263 | |||
| 264 | /** | ||
| 265 | * Set the digit list to a representation of the given double value. | ||
| 266 | * This method supports fixed-point notation. | ||
| 267 | * @param isNegative Boolean value indicating whether the number is negative. | ||
| 268 | * @param source Value to be converted; must not be Inf, -Inf, Nan, | ||
| 269 | * or a value <= 0. | ||
| 270 | * @param maximumFractionDigits The most fractional digits which should | ||
| 271 | * be converted. | ||
| 272 | */ | ||
| 273 | public final void set(boolean isNegative, double source, int maximumFractionDigits) { | ||
| 274 | set(isNegative, source, maximumFractionDigits, true); | ||
| 275 | } | ||
| 276 | |||
| 277 | /** | ||
| 278 | * Set the digit list to a representation of the given double value. | ||
| 279 | * This method supports both fixed-point and exponential notation. | ||
| 280 | * @param isNegative Boolean value indicating whether the number is negative. | ||
| 281 | * @param source Value to be converted; must not be Inf, -Inf, Nan, | ||
| 282 | * or a value <= 0. | ||
| 283 | * @param maximumDigits The most fractional or total digits which should | ||
| 284 | * be converted. | ||
| 285 | * @param fixedPoint If true, then maximumDigits is the maximum | ||
| 286 | * fractional digits to be converted. If false, total digits. | ||
| 287 | */ | ||
| 288 | final void set(boolean isNegative, double source, int maximumDigits, boolean fixedPoint) { | ||
| 289 | set(isNegative, Double.toString(source), maximumDigits, fixedPoint); | ||
| 290 | } | ||
| 291 | |||
| 292 | /** | ||
| 293 | * Generate a representation of the form DDDDD, DDDDD.DDDDD, or | ||
| 294 | * DDDDDE+/-DDDDD. | ||
| 295 | */ | ||
| 296 | final void set(boolean isNegative, String s, int maximumDigits, boolean fixedPoint) { | ||
| 297 | this.isNegative = isNegative; | ||
| 298 | int len = s.length(); | ||
| 299 | char[] source = getDataChars(len); | ||
| 300 | s.getChars(0, len, source, 0); | ||
| 301 | |||
| 302 | decimalAt = -1; | ||
| 303 | count = 0; | ||
| 304 | int exponent = 0; | ||
| 305 | // Number of zeros between decimal point and first non-zero digit after | ||
| 306 | // decimal point, for numbers < 1. | ||
| 307 | int leadingZerosAfterDecimal = 0; | ||
| 308 | boolean nonZeroDigitSeen = false; | ||
| 309 | |||
| 310 | for (int i = 0; i < len; ) { | ||
| 311 | char c = source[i++]; | ||
| 312 | if (c == '.') { | ||
| 313 | decimalAt = count; | ||
| 314 | } else if (c == 'e' || c == 'E') { | ||
| 315 | exponent = parseInt(source, i, len); | ||
| 316 | break; | ||
| 317 | } else { | ||
| 318 | if (!nonZeroDigitSeen) { | ||
| 319 | nonZeroDigitSeen = (c != '0'); | ||
| 320 | if (!nonZeroDigitSeen && decimalAt != -1) | ||
| 321 | ++leadingZerosAfterDecimal; | ||
| 322 | } | ||
| 323 | if (nonZeroDigitSeen) { | ||
| 324 | digits[count++] = c; | ||
| 325 | } | ||
| 326 | } | ||
| 327 | } | ||
| 328 | if (decimalAt == -1) { | ||
| 329 | decimalAt = count; | ||
| 330 | } | ||
| 331 | if (nonZeroDigitSeen) { | ||
| 332 | decimalAt += exponent - leadingZerosAfterDecimal; | ||
| 333 | } | ||
| 334 | |||
| 335 | if (fixedPoint) { | ||
| 336 | // The negative of the exponent represents the number of leading | ||
| 337 | // zeros between the decimal and the first non-zero digit, for | ||
| 338 | // a value < 0.1 (e.g., for 0.00123, -decimalAt == 2). If this | ||
| 339 | // is more than the maximum fraction digits, then we have an underflow | ||
| 340 | // for the printed representation. | ||
| 341 | if (-decimalAt > maximumDigits) { | ||
| 342 | // Handle an underflow to zero when we round something like | ||
| 343 | // 0.0009 to 2 fractional digits. | ||
| 344 | count = 0; | ||
| 345 | return; | ||
| 346 | } else if (-decimalAt == maximumDigits) { | ||
| 347 | // If we round 0.0009 to 3 fractional digits, then we have to | ||
| 348 | // create a new one digit in the least significant location. | ||
| 349 | if (shouldRoundUp(0)) { | ||
| 350 | count = 1; | ||
| 351 | ++decimalAt; | ||
| 352 | digits[0] = '1'; | ||
| 353 | } else { | ||
| 354 | count = 0; | ||
| 355 | } | ||
| 356 | return; | ||
| 357 | } | ||
| 358 | // else fall through | ||
| 359 | } | ||
| 360 | |||
| 361 | // Eliminate trailing zeros. | ||
| 362 | while (count > 1 && digits[count - 1] == '0') { | ||
| 363 | --count; | ||
| 364 | } | ||
| 365 | |||
| 366 | // Eliminate digits beyond maximum digits to be displayed. | ||
| 367 | // Round up if appropriate. | ||
| 368 | round(fixedPoint ? (maximumDigits + decimalAt) : maximumDigits); | ||
| 369 | } | ||
| 370 | |||
| 371 | /** | ||
| 372 | * Round the representation to the given number of digits. | ||
| 373 | * @param maximumDigits The maximum number of digits to be shown. | ||
| 374 | * Upon return, count will be less than or equal to maximumDigits. | ||
| 375 | */ | ||
| 376 | private final void round(int maximumDigits) { | ||
| 377 | // Eliminate digits beyond maximum digits to be displayed. | ||
| 378 | // Round up if appropriate. | ||
| 379 | if (maximumDigits >= 0 && maximumDigits < count) { | ||
| 380 | if (shouldRoundUp(maximumDigits)) { | ||
| 381 | // Rounding up involved incrementing digits from LSD to MSD. | ||
| 382 | // In most cases this is simple, but in a worst case situation | ||
| 383 | // (9999..99) we have to adjust the decimalAt value. | ||
| 384 | for (;;) { | ||
| 385 | --maximumDigits; | ||
| 386 | if (maximumDigits < 0) { | ||
| 387 | // We have all 9's, so we increment to a single digit | ||
| 388 | // of one and adjust the exponent. | ||
| 389 | digits[0] = '1'; | ||
| 390 | ++decimalAt; | ||
| 391 | maximumDigits = 0; // Adjust the count | ||
| 392 | break; | ||
| 393 | } | ||
| 394 | |||
| 395 | ++digits[maximumDigits]; | ||
| 396 | if (digits[maximumDigits] <= '9') break; | ||
| 397 | // digits[maximumDigits] = '0'; // Unnecessary since we'll truncate this | ||
| 398 | } | ||
| 399 | ++maximumDigits; // Increment for use as count | ||
| 400 | } | ||
| 401 | count = maximumDigits; | ||
| 402 | |||
| 403 | // Eliminate trailing zeros. | ||
| 404 | while (count > 1 && digits[count-1] == '0') { | ||
| 405 | --count; | ||
| 406 | } | ||
| 407 | } | ||
| 408 | } | ||
| 409 | |||
| 410 | |||
| 411 | /** | ||
| 412 | * Return true if truncating the representation to the given number | ||
| 413 | * of digits will result in an increment to the last digit. This | ||
| 414 | * method implements the rounding modes defined in the | ||
| 415 | * java.math.RoundingMode class. | ||
| 416 | * [bnf] | ||
| 417 | * @param maximumDigits the number of digits to keep, from 0 to | ||
| 418 | * <code>count-1</code>. If 0, then all digits are rounded away, and | ||
| 419 | * this method returns true if a one should be generated (e.g., formatting | ||
| 420 | * 0.09 with "#.#"). | ||
| 421 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 422 | * mode being set to RoundingMode.UNNECESSARY | ||
| 423 | * @return true if digit <code>maximumDigits-1</code> should be | ||
| 424 | * incremented | ||
| 425 | */ | ||
| 426 | private boolean shouldRoundUp(int maximumDigits) { | ||
| 427 | if (maximumDigits < count) { | ||
| 428 | switch(roundingMode) { | ||
| 429 | case UP: | ||
| 430 | for (int i=maximumDigits; i<count; ++i) { | ||
| 431 | if (digits[i] != '0') { | ||
| 432 | return true; | ||
| 433 | } | ||
| 434 | } | ||
| 435 | break; | ||
| 436 | case DOWN: | ||
| 437 | break; | ||
| 438 | case CEILING: | ||
| 439 | for (int i=maximumDigits; i<count; ++i) { | ||
| 440 | if (digits[i] != '0') { | ||
| 441 | return !isNegative; | ||
| 442 | } | ||
| 443 | } | ||
| 444 | break; | ||
| 445 | case FLOOR: | ||
| 446 | for (int i=maximumDigits; i<count; ++i) { | ||
| 447 | if (digits[i] != '0') { | ||
| 448 | return isNegative; | ||
| 449 | } | ||
| 450 | } | ||
| 451 | break; | ||
| 452 | case HALF_UP: | ||
| 453 | if (digits[maximumDigits] >= '5') { | ||
| 454 | return true; | ||
| 455 | } | ||
| 456 | break; | ||
| 457 | case HALF_DOWN: | ||
| 458 | if (digits[maximumDigits] > '5') { | ||
| 459 | return true; | ||
| 460 | } else if (digits[maximumDigits] == '5' ) { | ||
| 461 | for (int i=maximumDigits+1; i<count; ++i) { | ||
| 462 | if (digits[i] != '0') { | ||
| 463 | return true; | ||
| 464 | } | ||
| 465 | } | ||
| 466 | } | ||
| 467 | break; | ||
| 468 | case HALF_EVEN: | ||
| 469 | // Implement IEEE half-even rounding | ||
| 470 | if (digits[maximumDigits] > '5') { | ||
| 471 | return true; | ||
| 472 | } else if (digits[maximumDigits] == '5' ) { | ||
| 473 | for (int i=maximumDigits+1; i<count; ++i) { | ||
| 474 | if (digits[i] != '0') { | ||
| 475 | return true; | ||
| 476 | } | ||
| 477 | } | ||
| 478 | return maximumDigits > 0 && (digits[maximumDigits-1] % 2 != 0); | ||
| 479 | } | ||
| 480 | break; | ||
| 481 | case UNNECESSARY: | ||
| 482 | for (int i=maximumDigits; i<count; ++i) { | ||
| 483 | if (digits[i] != '0') { | ||
| 484 | throw new ArithmeticException( | ||
| 485 | "Rounding needed with the rounding mode being set to RoundingMode.UNNECESSARY"); | ||
| 486 | } | ||
| 487 | } | ||
| 488 | break; | ||
| 489 | default: | ||
| 490 | assert false; | ||
| 491 | } | ||
| 492 | } | ||
| 493 | return false; | ||
| 494 | } | ||
| 495 | |||
| 496 | /** | ||
| 497 | * Utility routine to set the value of the digit list from a long | ||
| 498 | */ | ||
| 499 | public final void set(boolean isNegative, long source) { | ||
| 500 | set(isNegative, source, 0); | ||
| 501 | } | ||
| 502 | |||
| 503 | /** | ||
| 504 | * Set the digit list to a representation of the given long value. | ||
| 505 | * @param isNegative Boolean value indicating whether the number is negative. | ||
| 506 | * @param source Value to be converted; must be >= 0 or == | ||
| 507 | * Long.MIN_VALUE. | ||
| 508 | * @param maximumDigits The most digits which should be converted. | ||
| 509 | * If maximumDigits is lower than the number of significant digits | ||
| 510 | * in source, the representation will be rounded. Ignored if <= 0. | ||
| 511 | */ | ||
| 512 | public final void set(boolean isNegative, long source, int maximumDigits) { | ||
| 513 | this.isNegative = isNegative; | ||
| 514 | |||
| 515 | // This method does not expect a negative number. However, | ||
| 516 | // "source" can be a Long.MIN_VALUE (-9223372036854775808), | ||
| 517 | // if the number being formatted is a Long.MIN_VALUE. In that | ||
| 518 | // case, it will be formatted as -Long.MIN_VALUE, a number | ||
| 519 | // which is outside the legal range of a long, but which can | ||
| 520 | // be represented by DigitList. | ||
| 521 | if (source <= 0) { | ||
| 522 | if (source == Long.MIN_VALUE) { | ||
| 523 | decimalAt = count = MAX_COUNT; | ||
| 524 | System.arraycopy(LONG_MIN_REP, 0, digits, 0, count); | ||
| 525 | } else { | ||
| 526 | decimalAt = count = 0; // Values <= 0 format as zero | ||
| 527 | } | ||
| 528 | } else { | ||
| 529 | // Rewritten to improve performance. I used to call | ||
| 530 | // Long.toString(), which was about 4x slower than this code. | ||
| 531 | int left = MAX_COUNT; | ||
| 532 | int right; | ||
| 533 | while (source > 0) { | ||
| 534 | digits[--left] = (char)('0' + (source % 10)); | ||
| 535 | source /= 10; | ||
| 536 | } | ||
| 537 | decimalAt = MAX_COUNT - left; | ||
| 538 | // Don't copy trailing zeros. We are guaranteed that there is at | ||
| 539 | // least one non-zero digit, so we don't have to check lower bounds. | ||
| 540 | for (right = MAX_COUNT - 1; digits[right] == '0'; --right) | ||
| 541 | ; | ||
| 542 | count = right - left + 1; | ||
| 543 | System.arraycopy(digits, left, digits, 0, count); | ||
| 544 | } | ||
| 545 | if (maximumDigits > 0) round(maximumDigits); | ||
| 546 | } | ||
| 547 | |||
| 548 | /** | ||
| 549 | * Set the digit list to a representation of the given BigDecimal value. | ||
| 550 | * This method supports both fixed-point and exponential notation. | ||
| 551 | * @param isNegative Boolean value indicating whether the number is negative. | ||
| 552 | * @param source Value to be converted; must not be a value <= 0. | ||
| 553 | * @param maximumDigits The most fractional or total digits which should | ||
| 554 | * be converted. | ||
| 555 | * @param fixedPoint If true, then maximumDigits is the maximum | ||
| 556 | * fractional digits to be converted. If false, total digits. | ||
| 557 | */ | ||
| 558 | final void set(boolean isNegative, BigDecimal source, int maximumDigits, boolean fixedPoint) { | ||
| 559 | String s = source.toString(); | ||
| 560 | extendDigits(s.length()); | ||
| 561 | |||
| 562 | set(isNegative, s, maximumDigits, fixedPoint); | ||
| 563 | } | ||
| 564 | |||
| 565 | /** | ||
| 566 | * Set the digit list to a representation of the given BigInteger value. | ||
| 567 | * @param isNegative Boolean value indicating whether the number is negative. | ||
| 568 | * @param source Value to be converted; must be >= 0. | ||
| 569 | * @param maximumDigits The most digits which should be converted. | ||
| 570 | * If maximumDigits is lower than the number of significant digits | ||
| 571 | * in source, the representation will be rounded. Ignored if <= 0. | ||
| 572 | */ | ||
| 573 | final void set(boolean isNegative, BigInteger source, int maximumDigits) { | ||
| 574 | this.isNegative = isNegative; | ||
| 575 | String s = source.toString(); | ||
| 576 | int len = s.length(); | ||
| 577 | extendDigits(len); | ||
| 578 | s.getChars(0, len, digits, 0); | ||
| 579 | |||
| 580 | decimalAt = len; | ||
| 581 | int right; | ||
| 582 | for (right = len - 1; right >= 0 && digits[right] == '0'; --right) | ||
| 583 | ; | ||
| 584 | count = right + 1; | ||
| 585 | |||
| 586 | if (maximumDigits > 0) { | ||
| 587 | round(maximumDigits); | ||
| 588 | } | ||
| 589 | } | ||
| 590 | |||
| 591 | /** | ||
| 592 | * equality test between two digit lists. | ||
| 593 | */ | ||
| 594 | public boolean equals(Object obj) { | ||
| 595 | if (this == obj) // quick check | ||
| 596 | return true; | ||
| 597 | if (!(obj instanceof DigitList)) // (1) same object? | ||
| 598 | return false; | ||
| 599 | DigitList other = (DigitList) obj; | ||
| 600 | if (count != other.count || | ||
| 601 | decimalAt != other.decimalAt) | ||
| 602 | return false; | ||
| 603 | for (int i = 0; i < count; i++) | ||
| 604 | if (digits[i] != other.digits[i]) | ||
| 605 | return false; | ||
| 606 | return true; | ||
| 607 | } | ||
| 608 | |||
| 609 | /** | ||
| 610 | * Generates the hash code for the digit list. | ||
| 611 | */ | ||
| 612 | public int hashCode() { | ||
| 613 | int hashcode = decimalAt; | ||
| 614 | |||
| 615 | for (int i = 0; i < count; i++) { | ||
| 616 | hashcode = hashcode * 37 + digits[i]; | ||
| 617 | } | ||
| 618 | |||
| 619 | return hashcode; | ||
| 620 | } | ||
| 621 | |||
| 622 | /** | ||
| 623 | * Creates a copy of this object. | ||
| 624 | * @return a clone of this instance. | ||
| 625 | */ | ||
| 626 | public Object clone() { | ||
| 627 | try { | ||
| 628 | DigitList other = (DigitList) super.clone(); | ||
| 629 | char[] newDigits = new char[digits.length]; | ||
| 630 | System.arraycopy(digits, 0, newDigits, 0, digits.length); | ||
| 631 | other.digits = newDigits; | ||
| 632 | other.tempBuffer = null; | ||
| 633 | return other; | ||
| 634 | } catch (CloneNotSupportedException e) { | ||
| 635 | throw new InternalError(); | ||
| 636 | } | ||
| 637 | } | ||
| 638 | |||
| 639 | /** | ||
| 640 | * Returns true if this DigitList represents Long.MIN_VALUE; | ||
| 641 | * false, otherwise. This is required so that getLong() works. | ||
| 642 | */ | ||
| 643 | private boolean isLongMIN_VALUE() { | ||
| 644 | if (decimalAt != count || count != MAX_COUNT) { | ||
| 645 | return false; | ||
| 646 | } | ||
| 647 | |||
| 648 | for (int i = 0; i < count; ++i) { | ||
| 649 | if (digits[i] != LONG_MIN_REP[i]) return false; | ||
| 650 | } | ||
| 651 | |||
| 652 | return true; | ||
| 653 | } | ||
| 654 | |||
| 655 | private static final int parseInt(char[] str, int offset, int strLen) { | ||
| 656 | char c; | ||
| 657 | boolean positive = true; | ||
| 658 | if ((c = str[offset]) == '-') { | ||
| 659 | positive = false; | ||
| 660 | offset++; | ||
| 661 | } else if (c == '+') { | ||
| 662 | offset++; | ||
| 663 | } | ||
| 664 | |||
| 665 | int value = 0; | ||
| 666 | while (offset < strLen) { | ||
| 667 | c = str[offset++]; | ||
| 668 | if (c >= '0' && c <= '9') { | ||
| 669 | value = value * 10 + (c - '0'); | ||
| 670 | } else { | ||
| 671 | break; | ||
| 672 | } | ||
| 673 | } | ||
| 674 | return positive ? value : -value; | ||
| 675 | } | ||
| 676 | |||
| 677 | // The digit part of -9223372036854775808L | ||
| 678 | private static final char[] LONG_MIN_REP = "9223372036854775808".toCharArray(); | ||
| 679 | |||
| 680 | public String toString() { | ||
| 681 | if (isZero()) { | ||
| 682 | return "0"; | ||
| 683 | } | ||
| 684 | StringBuffer buf = getStringBuffer(); | ||
| 685 | buf.append("0."); | ||
| 686 | buf.append(digits, 0, count); | ||
| 687 | buf.append("x10^"); | ||
| 688 | buf.append(decimalAt); | ||
| 689 | return buf.toString(); | ||
| 690 | } | ||
| 691 | |||
| 692 | private StringBuffer tempBuffer; | ||
| 693 | |||
| 694 | private StringBuffer getStringBuffer() { | ||
| 695 | if (tempBuffer == null) { | ||
| 696 | tempBuffer = new StringBuffer(MAX_COUNT); | ||
| 697 | } else { | ||
| 698 | tempBuffer.setLength(0); | ||
| 699 | } | ||
| 700 | return tempBuffer; | ||
| 701 | } | ||
| 702 | |||
| 703 | private void extendDigits(int len) { | ||
| 704 | if (len > digits.length) { | ||
| 705 | digits = new char[len]; | ||
| 706 | } | ||
| 707 | } | ||
| 708 | |||
| 709 | private final char[] getDataChars(int length) { | ||
| 710 | if (data == null || data.length < length) { | ||
| 711 | data = new char[length]; | ||
| 712 | } | ||
| 713 | return data; | ||
| 714 | } | ||
| 715 | } | ||
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,53 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | package java.text; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * DontCareFieldPosition defines no-op FieldDelegate. Its | ||
| 30 | * singleton is used for the format methods that don't take a | ||
| 31 | * FieldPosition. | ||
| 32 | */ | ||
| 33 | class DontCareFieldPosition extends FieldPosition { | ||
| 34 | // The singleton of DontCareFieldPosition. | ||
| 35 | static final FieldPosition INSTANCE = new DontCareFieldPosition(); | ||
| 36 | |||
| 37 | private final Format.FieldDelegate noDelegate = new Format.FieldDelegate() { | ||
| 38 | public void formatted(Format.Field attr, Object value, int start, | ||
| 39 | int end, StringBuffer buffer) { | ||
| 40 | } | ||
| 41 | public void formatted(int fieldID, Format.Field attr, Object value, | ||
| 42 | int start, int end, StringBuffer buffer) { | ||
| 43 | } | ||
| 44 | }; | ||
| 45 | |||
| 46 | private DontCareFieldPosition() { | ||
| 47 | super(0); | ||
| 48 | } | ||
| 49 | |||
| 50 | Format.FieldDelegate getFieldDelegate() { | ||
| 51 | return noDelegate; | ||
| 52 | } | ||
| 53 | } | ||
Binary file not shown.
| @@ -0,0 +1,60 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 1998, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | /** | ||
| 42 | * This is used for building contracting character tables. entryName | ||
| 43 | * is the contracting character name and value is its collation | ||
| 44 | * order. | ||
| 45 | */ | ||
| 46 | final class EntryPair | ||
| 47 | { | ||
| 48 | public String entryName; | ||
| 49 | public int value; | ||
| 50 | public boolean fwd; | ||
| 51 | |||
| 52 | public EntryPair(String name, int value) { | ||
| 53 | this(name, value, true); | ||
| 54 | } | ||
| 55 | public EntryPair(String name, int value, boolean fwd) { | ||
| 56 | this.entryName = name; | ||
| 57 | this.value = value; | ||
| 58 | this.fwd = fwd; | ||
| 59 | } | ||
| 60 | } | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,303 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2002, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | /** | ||
| 42 | * <code>FieldPosition</code> is a simple class used by <code>Format</code> | ||
| 43 | * and its subclasses to identify fields in formatted output. Fields can | ||
| 44 | * be identified in two ways: | ||
| 45 | * <ul> | ||
| 46 | * <li>By an integer constant, whose names typically end with | ||
| 47 | * <code>_FIELD</code>. The constants are defined in the various | ||
| 48 | * subclasses of <code>Format</code>. | ||
| 49 | * <li>By a <code>Format.Field</code> constant, see <code>ERA_FIELD</code> | ||
| 50 | * and its friends in <code>DateFormat</code> for an example. | ||
| 51 | * </ul> | ||
| 52 | * <p> | ||
| 53 | * <code>FieldPosition</code> keeps track of the position of the | ||
| 54 | * field within the formatted output with two indices: the index | ||
| 55 | * of the first character of the field and the index of the last | ||
| 56 | * character of the field. | ||
| 57 | * | ||
| 58 | * <p> | ||
| 59 | * One version of the <code>format</code> method in the various | ||
| 60 | * <code>Format</code> classes requires a <code>FieldPosition</code> | ||
| 61 | * object as an argument. You use this <code>format</code> method | ||
| 62 | * to perform partial formatting or to get information about the | ||
| 63 | * formatted output (such as the position of a field). | ||
| 64 | * | ||
| 65 | * <p> | ||
| 66 | * If you are interested in the positions of all attributes in the | ||
| 67 | * formatted string use the <code>Format</code> method | ||
| 68 | * <code>formatToCharacterIterator</code>. | ||
| 69 | * | ||
| 70 | * @author Mark Davis | ||
| 71 | * @see java.text.Format | ||
| 72 | */ | ||
| 73 | public class FieldPosition { | ||
| 74 | |||
| 75 | /** | ||
| 76 | * Input: Desired field to determine start and end offsets for. | ||
| 77 | * The meaning depends on the subclass of Format. | ||
| 78 | */ | ||
| 79 | int field = 0; | ||
| 80 | |||
| 81 | /** | ||
| 82 | * Output: End offset of field in text. | ||
| 83 | * If the field does not occur in the text, 0 is returned. | ||
| 84 | */ | ||
| 85 | int endIndex = 0; | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Output: Start offset of field in text. | ||
| 89 | * If the field does not occur in the text, 0 is returned. | ||
| 90 | */ | ||
| 91 | int beginIndex = 0; | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Desired field this FieldPosition is for. | ||
| 95 | */ | ||
| 96 | private Format.Field attribute; | ||
| 97 | |||
| 98 | /** | ||
| 99 | * Creates a FieldPosition object for the given field. Fields are | ||
| 100 | * identified by constants, whose names typically end with _FIELD, | ||
| 101 | * in the various subclasses of Format. | ||
| 102 | * | ||
| 103 | * @see java.text.NumberFormat#INTEGER_FIELD | ||
| 104 | * @see java.text.NumberFormat#FRACTION_FIELD | ||
| 105 | * @see java.text.DateFormat#YEAR_FIELD | ||
| 106 | * @see java.text.DateFormat#MONTH_FIELD | ||
| 107 | */ | ||
| 108 | public FieldPosition(int field) { | ||
| 109 | this.field = field; | ||
| 110 | } | ||
| 111 | |||
| 112 | /** | ||
| 113 | * Creates a FieldPosition object for the given field constant. Fields are | ||
| 114 | * identified by constants defined in the various <code>Format</code> | ||
| 115 | * subclasses. This is equivalent to calling | ||
| 116 | * <code>new FieldPosition(attribute, -1)</code>. | ||
| 117 | * | ||
| 118 | * @param attribute Format.Field constant identifying a field | ||
| 119 | * @since 1.4 | ||
| 120 | */ | ||
| 121 | public FieldPosition(Format.Field attribute) { | ||
| 122 | this(attribute, -1); | ||
| 123 | } | ||
| 124 | |||
| 125 | /** | ||
| 126 | * Creates a <code>FieldPosition</code> object for the given field. | ||
| 127 | * The field is identified by an attribute constant from one of the | ||
| 128 | * <code>Field</code> subclasses as well as an integer field ID | ||
| 129 | * defined by the <code>Format</code> subclasses. <code>Format</code> | ||
| 130 | * subclasses that are aware of <code>Field</code> should give precedence | ||
| 131 | * to <code>attribute</code> and ignore <code>fieldID</code> if | ||
| 132 | * <code>attribute</code> is not null. However, older <code>Format</code> | ||
| 133 | * subclasses may not be aware of <code>Field</code> and rely on | ||
| 134 | * <code>fieldID</code>. If the field has no corresponding integer | ||
| 135 | * constant, <code>fieldID</code> should be -1. | ||
| 136 | * | ||
| 137 | * @param attribute Format.Field constant identifying a field | ||
| 138 | * @param fieldID integer constantce identifying a field | ||
| 139 | * @since 1.4 | ||
| 140 | */ | ||
| 141 | public FieldPosition(Format.Field attribute, int fieldID) { | ||
| 142 | this.attribute = attribute; | ||
| 143 | this.field = fieldID; | ||
| 144 | } | ||
| 145 | |||
| 146 | /** | ||
| 147 | * Returns the field identifier as an attribute constant | ||
| 148 | * from one of the <code>Field</code> subclasses. May return null if | ||
| 149 | * the field is specified only by an integer field ID. | ||
| 150 | * | ||
| 151 | * @return Identifier for the field | ||
| 152 | * @since 1.4 | ||
| 153 | */ | ||
| 154 | public Format.Field getFieldAttribute() { | ||
| 155 | return attribute; | ||
| 156 | } | ||
| 157 | |||
| 158 | /** | ||
| 159 | * Retrieves the field identifier. | ||
| 160 | */ | ||
| 161 | public int getField() { | ||
| 162 | return field; | ||
| 163 | } | ||
| 164 | |||
| 165 | /** | ||
| 166 | * Retrieves the index of the first character in the requested field. | ||
| 167 | */ | ||
| 168 | public int getBeginIndex() { | ||
| 169 | return beginIndex; | ||
| 170 | } | ||
| 171 | |||
| 172 | /** | ||
| 173 | * Retrieves the index of the character following the last character in the | ||
| 174 | * requested field. | ||
| 175 | */ | ||
| 176 | public int getEndIndex() { | ||
| 177 | return endIndex; | ||
| 178 | } | ||
| 179 | |||
| 180 | /** | ||
| 181 | * Sets the begin index. For use by subclasses of Format. | ||
| 182 | * @since 1.2 | ||
| 183 | */ | ||
| 184 | public void setBeginIndex(int bi) { | ||
| 185 | beginIndex = bi; | ||
| 186 | } | ||
| 187 | |||
| 188 | /** | ||
| 189 | * Sets the end index. For use by subclasses of Format. | ||
| 190 | * @since 1.2 | ||
| 191 | */ | ||
| 192 | public void setEndIndex(int ei) { | ||
| 193 | endIndex = ei; | ||
| 194 | } | ||
| 195 | |||
| 196 | /** | ||
| 197 | * Returns a <code>Format.FieldDelegate</code> instance that is associated | ||
| 198 | * with the FieldPosition. When the delegate is notified of the same | ||
| 199 | * field the FieldPosition is associated with, the begin/end will be | ||
| 200 | * adjusted. | ||
| 201 | */ | ||
| 202 | Format.FieldDelegate getFieldDelegate() { | ||
| 203 | return new Delegate(); | ||
| 204 | } | ||
| 205 | |||
| 206 | /** | ||
| 207 | * Overrides equals | ||
| 208 | */ | ||
| 209 | public boolean equals(Object obj) | ||
| 210 | { | ||
| 211 | if (obj == null) return false; | ||
| 212 | if (!(obj instanceof FieldPosition)) | ||
| 213 | return false; | ||
| 214 | FieldPosition other = (FieldPosition) obj; | ||
| 215 | if (attribute == null) { | ||
| 216 | if (other.attribute != null) { | ||
| 217 | return false; | ||
| 218 | } | ||
| 219 | } | ||
| 220 | else if (!attribute.equals(other.attribute)) { | ||
| 221 | return false; | ||
| 222 | } | ||
| 223 | return (beginIndex == other.beginIndex | ||
| 224 | && endIndex == other.endIndex | ||
| 225 | && field == other.field); | ||
| 226 | } | ||
| 227 | |||
| 228 | /** | ||
| 229 | * Returns a hash code for this FieldPosition. | ||
| 230 | * @return a hash code value for this object | ||
| 231 | */ | ||
| 232 | public int hashCode() { | ||
| 233 | return (field << 24) | (beginIndex << 16) | endIndex; | ||
| 234 | } | ||
| 235 | |||
| 236 | /** | ||
| 237 | * Return a string representation of this FieldPosition. | ||
| 238 | * @return a string representation of this object | ||
| 239 | */ | ||
| 240 | public String toString() { | ||
| 241 | return getClass().getName() + | ||
| 242 | "[field=" + field + ",attribute=" + attribute + | ||
| 243 | ",beginIndex=" + beginIndex + | ||
| 244 | ",endIndex=" + endIndex + ']'; | ||
| 245 | } | ||
| 246 | |||
| 247 | |||
| 248 | /** | ||
| 249 | * Return true if the receiver wants a <code>Format.Field</code> value and | ||
| 250 | * <code>attribute</code> is equal to it. | ||
| 251 | */ | ||
| 252 | private boolean matchesField(Format.Field attribute) { | ||
| 253 | if (this.attribute != null) { | ||
| 254 | return this.attribute.equals(attribute); | ||
| 255 | } | ||
| 256 | return false; | ||
| 257 | } | ||
| 258 | |||
| 259 | /** | ||
| 260 | * Return true if the receiver wants a <code>Format.Field</code> value and | ||
| 261 | * <code>attribute</code> is equal to it, or true if the receiver | ||
| 262 | * represents an inteter constant and <code>field</code> equals it. | ||
| 263 | */ | ||
| 264 | private boolean matchesField(Format.Field attribute, int field) { | ||
| 265 | if (this.attribute != null) { | ||
| 266 | return this.attribute.equals(attribute); | ||
| 267 | } | ||
| 268 | return (field == this.field); | ||
| 269 | } | ||
| 270 | |||
| 271 | |||
| 272 | /** | ||
| 273 | * An implementation of FieldDelegate that will adjust the begin/end | ||
| 274 | * of the FieldPosition if the arguments match the field of | ||
| 275 | * the FieldPosition. | ||
| 276 | */ | ||
| 277 | private class Delegate implements Format.FieldDelegate { | ||
| 278 | /** | ||
| 279 | * Indicates whether the field has been encountered before. If this | ||
| 280 | * is true, and <code>formatted</code> is invoked, the begin/end | ||
| 281 | * are not updated. | ||
| 282 | */ | ||
| 283 | private boolean encounteredField; | ||
| 284 | |||
| 285 | public void formatted(Format.Field attr, Object value, int start, | ||
| 286 | int end, StringBuffer buffer) { | ||
| 287 | if (!encounteredField && matchesField(attr)) { | ||
| 288 | setBeginIndex(start); | ||
| 289 | setEndIndex(end); | ||
| 290 | encounteredField = (start != end); | ||
| 291 | } | ||
| 292 | } | ||
| 293 | |||
| 294 | public void formatted(int fieldID, Format.Field attr, Object value, | ||
| 295 | int start, int end, StringBuffer buffer) { | ||
| 296 | if (!encounteredField && matchesField(attr, fieldID)) { | ||
| 297 | setBeginIndex(start); | ||
| 298 | setEndIndex(end); | ||
| 299 | encounteredField = (start != end); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | } | ||
| 303 | } | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,406 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2005, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.io.Serializable; | ||
| 42 | |||
| 43 | /** | ||
| 44 | * <code>Format</code> is an abstract base class for formatting locale-sensitive | ||
| 45 | * information such as dates, messages, and numbers. | ||
| 46 | * | ||
| 47 | * <p> | ||
| 48 | * <code>Format</code> defines the programming interface for formatting | ||
| 49 | * locale-sensitive objects into <code>String</code>s (the | ||
| 50 | * <code>format</code> method) and for parsing <code>String</code>s back | ||
| 51 | * into objects (the <code>parseObject</code> method). | ||
| 52 | * | ||
| 53 | * <p> | ||
| 54 | * Generally, a format's <code>parseObject</code> method must be able to parse | ||
| 55 | * any string formatted by its <code>format</code> method. However, there may | ||
| 56 | * be exceptional cases where this is not possible. For example, a | ||
| 57 | * <code>format</code> method might create two adjacent integer numbers with | ||
| 58 | * no separator in between, and in this case the <code>parseObject</code> could | ||
| 59 | * not tell which digits belong to which number. | ||
| 60 | * | ||
| 61 | * <h4>Subclassing</h4> | ||
| 62 | * | ||
| 63 | * <p> | ||
| 64 | * The Java Platform provides three specialized subclasses of <code>Format</code>-- | ||
| 65 | * <code>DateFormat</code>, <code>MessageFormat</code>, and | ||
| 66 | * <code>NumberFormat</code>--for formatting dates, messages, and numbers, | ||
| 67 | * respectively. | ||
| 68 | * <p> | ||
| 69 | * Concrete subclasses must implement three methods: | ||
| 70 | * <ol> | ||
| 71 | * <li> <code>format(Object obj, StringBuffer toAppendTo, FieldPosition pos)</code> | ||
| 72 | * <li> <code>formatToCharacterIterator(Object obj)</code> | ||
| 73 | * <li> <code>parseObject(String source, ParsePosition pos)</code> | ||
| 74 | * </ol> | ||
| 75 | * These general methods allow polymorphic parsing and formatting of objects | ||
| 76 | * and are used, for example, by <code>MessageFormat</code>. | ||
| 77 | * Subclasses often also provide additional <code>format</code> methods for | ||
| 78 | * specific input types as well as <code>parse</code> methods for specific | ||
| 79 | * result types. Any <code>parse</code> method that does not take a | ||
| 80 | * <code>ParsePosition</code> argument should throw <code>ParseException</code> | ||
| 81 | * when no text in the required format is at the beginning of the input text. | ||
| 82 | * | ||
| 83 | * <p> | ||
| 84 | * Most subclasses will also implement the following factory methods: | ||
| 85 | * <ol> | ||
| 86 | * <li> | ||
| 87 | * <code>getInstance</code> for getting a useful format object appropriate | ||
| 88 | * for the current locale | ||
| 89 | * <li> | ||
| 90 | * <code>getInstance(Locale)</code> for getting a useful format | ||
| 91 | * object appropriate for the specified locale | ||
| 92 | * </ol> | ||
| 93 | * In addition, some subclasses may also implement other | ||
| 94 | * <code>getXxxxInstance</code> methods for more specialized control. For | ||
| 95 | * example, the <code>NumberFormat</code> class provides | ||
| 96 | * <code>getPercentInstance</code> and <code>getCurrencyInstance</code> | ||
| 97 | * methods for getting specialized number formatters. | ||
| 98 | * | ||
| 99 | * <p> | ||
| 100 | * Subclasses of <code>Format</code> that allow programmers to create objects | ||
| 101 | * for locales (with <code>getInstance(Locale)</code> for example) | ||
| 102 | * must also implement the following class method: | ||
| 103 | * <blockquote> | ||
| 104 | * <pre> | ||
| 105 | * public static Locale[] getAvailableLocales() | ||
| 106 | * </pre> | ||
| 107 | * </blockquote> | ||
| 108 | * | ||
| 109 | * <p> | ||
| 110 | * And finally subclasses may define a set of constants to identify the various | ||
| 111 | * fields in the formatted output. These constants are used to create a FieldPosition | ||
| 112 | * object which identifies what information is contained in the field and its | ||
| 113 | * position in the formatted result. These constants should be named | ||
| 114 | * <code><em>item</em>_FIELD</code> where <code><em>item</em></code> identifies | ||
| 115 | * the field. For examples of these constants, see <code>ERA_FIELD</code> and its | ||
| 116 | * friends in {@link DateFormat}. | ||
| 117 | * | ||
| 118 | * <h4><a name="synchronization">Synchronization</a></h4> | ||
| 119 | * | ||
| 120 | * <p> | ||
| 121 | * Formats are generally not synchronized. | ||
| 122 | * It is recommended to create separate format instances for each thread. | ||
| 123 | * If multiple threads access a format concurrently, it must be synchronized | ||
| 124 | * externally. | ||
| 125 | * | ||
| 126 | * @see java.text.ParsePosition | ||
| 127 | * @see java.text.FieldPosition | ||
| 128 | * @see java.text.NumberFormat | ||
| 129 | * @see java.text.DateFormat | ||
| 130 | * @see java.text.MessageFormat | ||
| 131 | * @author Mark Davis | ||
| 132 | */ | ||
| 133 | public abstract class Format implements Serializable, Cloneable { | ||
| 134 | |||
| 135 | private static final long serialVersionUID = -299282585814624189L; | ||
| 136 | |||
| 137 | /** | ||
| 138 | * Sole constructor. (For invocation by subclass constructors, typically | ||
| 139 | * implicit.) | ||
| 140 | */ | ||
| 141 | protected Format() { | ||
| 142 | } | ||
| 143 | |||
| 144 | /** | ||
| 145 | * Formats an object to produce a string. This is equivalent to | ||
| 146 | * <blockquote> | ||
| 147 | * {@link #format(Object, StringBuffer, FieldPosition) format}<code>(obj, | ||
| 148 | * new StringBuffer(), new FieldPosition(0)).toString();</code> | ||
| 149 | * </blockquote> | ||
| 150 | * | ||
| 151 | * @param obj The object to format | ||
| 152 | * @return Formatted string. | ||
| 153 | * @exception IllegalArgumentException if the Format cannot format the given | ||
| 154 | * object | ||
| 155 | */ | ||
| 156 | public final String format (Object obj) { | ||
| 157 | return format(obj, new StringBuffer(), new FieldPosition(0)).toString(); | ||
| 158 | } | ||
| 159 | |||
| 160 | /** | ||
| 161 | * Formats an object and appends the resulting text to a given string | ||
| 162 | * buffer. | ||
| 163 | * If the <code>pos</code> argument identifies a field used by the format, | ||
| 164 | * then its indices are set to the beginning and end of the first such | ||
| 165 | * field encountered. | ||
| 166 | * | ||
| 167 | * @param obj The object to format | ||
| 168 | * @param toAppendTo where the text is to be appended | ||
| 169 | * @param pos A <code>FieldPosition</code> identifying a field | ||
| 170 | * in the formatted text | ||
| 171 | * @return the string buffer passed in as <code>toAppendTo</code>, | ||
| 172 | * with formatted text appended | ||
| 173 | * @exception NullPointerException if <code>toAppendTo</code> or | ||
| 174 | * <code>pos</code> is null | ||
| 175 | * @exception IllegalArgumentException if the Format cannot format the given | ||
| 176 | * object | ||
| 177 | */ | ||
| 178 | public abstract StringBuffer format(Object obj, | ||
| 179 | StringBuffer toAppendTo, | ||
| 180 | FieldPosition pos); | ||
| 181 | |||
| 182 | /** | ||
| 183 | * Formats an Object producing an <code>AttributedCharacterIterator</code>. | ||
| 184 | * You can use the returned <code>AttributedCharacterIterator</code> | ||
| 185 | * to build the resulting String, as well as to determine information | ||
| 186 | * about the resulting String. | ||
| 187 | * <p> | ||
| 188 | * Each attribute key of the AttributedCharacterIterator will be of type | ||
| 189 | * <code>Field</code>. It is up to each <code>Format</code> implementation | ||
| 190 | * to define what the legal values are for each attribute in the | ||
| 191 | * <code>AttributedCharacterIterator</code>, but typically the attribute | ||
| 192 | * key is also used as the attribute value. | ||
| 193 | * <p>The default implementation creates an | ||
| 194 | * <code>AttributedCharacterIterator</code> with no attributes. Subclasses | ||
| 195 | * that support fields should override this and create an | ||
| 196 | * <code>AttributedCharacterIterator</code> with meaningful attributes. | ||
| 197 | * | ||
| 198 | * @exception NullPointerException if obj is null. | ||
| 199 | * @exception IllegalArgumentException when the Format cannot format the | ||
| 200 | * given object. | ||
| 201 | * @param obj The object to format | ||
| 202 | * @return AttributedCharacterIterator describing the formatted value. | ||
| 203 | * @since 1.4 | ||
| 204 | */ | ||
| 205 | public AttributedCharacterIterator formatToCharacterIterator(Object obj) { | ||
| 206 | return createAttributedCharacterIterator(format(obj)); | ||
| 207 | } | ||
| 208 | |||
| 209 | /** | ||
| 210 | * Parses text from a string to produce an object. | ||
| 211 | * <p> | ||
| 212 | * The method attempts to parse text starting at the index given by | ||
| 213 | * <code>pos</code>. | ||
| 214 | * If parsing succeeds, then the index of <code>pos</code> is updated | ||
| 215 | * to the index after the last character used (parsing does not necessarily | ||
| 216 | * use all characters up to the end of the string), and the parsed | ||
| 217 | * object is returned. The updated <code>pos</code> can be used to | ||
| 218 | * indicate the starting point for the next call to this method. | ||
| 219 | * If an error occurs, then the index of <code>pos</code> is not | ||
| 220 | * changed, the error index of <code>pos</code> is set to the index of | ||
| 221 | * the character where the error occurred, and null is returned. | ||
| 222 | * | ||
| 223 | * @param source A <code>String</code>, part of which should be parsed. | ||
| 224 | * @param pos A <code>ParsePosition</code> object with index and error | ||
| 225 | * index information as described above. | ||
| 226 | * @return An <code>Object</code> parsed from the string. In case of | ||
| 227 | * error, returns null. | ||
| 228 | * @exception NullPointerException if <code>pos</code> is null. | ||
| 229 | */ | ||
| 230 | public abstract Object parseObject (String source, ParsePosition pos); | ||
| 231 | |||
| 232 | /** | ||
| 233 | * Parses text from the beginning of the given string to produce an object. | ||
| 234 | * The method may not use the entire text of the given string. | ||
| 235 | * | ||
| 236 | * @param source A <code>String</code> whose beginning should be parsed. | ||
| 237 | * @return An <code>Object</code> parsed from the string. | ||
| 238 | * @exception ParseException if the beginning of the specified string | ||
| 239 | * cannot be parsed. | ||
| 240 | */ | ||
| 241 | public Object parseObject(String source) throws ParseException { | ||
| 242 | ParsePosition pos = new ParsePosition(0); | ||
| 243 | Object result = parseObject(source, pos); | ||
| 244 | if (pos.index == 0) { | ||
| 245 | throw new ParseException("Format.parseObject(String) failed", | ||
| 246 | pos.errorIndex); | ||
| 247 | } | ||
| 248 | return result; | ||
| 249 | } | ||
| 250 | |||
| 251 | /** | ||
| 252 | * Creates and returns a copy of this object. | ||
| 253 | * | ||
| 254 | * @return a clone of this instance. | ||
| 255 | */ | ||
| 256 | public Object clone() { | ||
| 257 | try { | ||
| 258 | return super.clone(); | ||
| 259 | } catch (CloneNotSupportedException e) { | ||
| 260 | // will never happen | ||
| 261 | return null; | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | // | ||
| 266 | // Convenience methods for creating AttributedCharacterIterators from | ||
| 267 | // different parameters. | ||
| 268 | // | ||
| 269 | |||
| 270 | /** | ||
| 271 | * Creates an <code>AttributedCharacterIterator</code> for the String | ||
| 272 | * <code>s</code>. | ||
| 273 | * | ||
| 274 | * @param s String to create AttributedCharacterIterator from | ||
| 275 | * @return AttributedCharacterIterator wrapping s | ||
| 276 | */ | ||
| 277 | AttributedCharacterIterator createAttributedCharacterIterator(String s) { | ||
| 278 | AttributedString as = new AttributedString(s); | ||
| 279 | |||
| 280 | return as.getIterator(); | ||
| 281 | } | ||
| 282 | |||
| 283 | /** | ||
| 284 | * Creates an <code>AttributedCharacterIterator</code> containg the | ||
| 285 | * concatenated contents of the passed in | ||
| 286 | * <code>AttributedCharacterIterator</code>s. | ||
| 287 | * | ||
| 288 | * @param iterators AttributedCharacterIterators used to create resulting | ||
| 289 | * AttributedCharacterIterators | ||
| 290 | * @return AttributedCharacterIterator wrapping passed in | ||
| 291 | * AttributedCharacterIterators | ||
| 292 | */ | ||
| 293 | AttributedCharacterIterator createAttributedCharacterIterator( | ||
| 294 | AttributedCharacterIterator[] iterators) { | ||
| 295 | AttributedString as = new AttributedString(iterators); | ||
| 296 | |||
| 297 | return as.getIterator(); | ||
| 298 | } | ||
| 299 | |||
| 300 | /** | ||
| 301 | * Returns an AttributedCharacterIterator with the String | ||
| 302 | * <code>string</code> and additional key/value pair <code>key</code>, | ||
| 303 | * <code>value</code>. | ||
| 304 | * | ||
| 305 | * @param string String to create AttributedCharacterIterator from | ||
| 306 | * @param key Key for AttributedCharacterIterator | ||
| 307 | * @param value Value associated with key in AttributedCharacterIterator | ||
| 308 | * @return AttributedCharacterIterator wrapping args | ||
| 309 | */ | ||
| 310 | AttributedCharacterIterator createAttributedCharacterIterator( | ||
| 311 | String string, AttributedCharacterIterator.Attribute key, | ||
| 312 | Object value) { | ||
| 313 | AttributedString as = new AttributedString(string); | ||
| 314 | |||
| 315 | as.addAttribute(key, value); | ||
| 316 | return as.getIterator(); | ||
| 317 | } | ||
| 318 | |||
| 319 | /** | ||
| 320 | * Creates an AttributedCharacterIterator with the contents of | ||
| 321 | * <code>iterator</code> and the additional attribute <code>key</code> | ||
| 322 | * <code>value</code>. | ||
| 323 | * | ||
| 324 | * @param iterator Initial AttributedCharacterIterator to add arg to | ||
| 325 | * @param key Key for AttributedCharacterIterator | ||
| 326 | * @param value Value associated with key in AttributedCharacterIterator | ||
| 327 | * @return AttributedCharacterIterator wrapping args | ||
| 328 | */ | ||
| 329 | AttributedCharacterIterator createAttributedCharacterIterator( | ||
| 330 | AttributedCharacterIterator iterator, | ||
| 331 | AttributedCharacterIterator.Attribute key, Object value) { | ||
| 332 | AttributedString as = new AttributedString(iterator); | ||
| 333 | |||
| 334 | as.addAttribute(key, value); | ||
| 335 | return as.getIterator(); | ||
| 336 | } | ||
| 337 | |||
| 338 | |||
| 339 | /** | ||
| 340 | * Defines constants that are used as attribute keys in the | ||
| 341 | * <code>AttributedCharacterIterator</code> returned | ||
| 342 | * from <code>Format.formatToCharacterIterator</code> and as | ||
| 343 | * field identifiers in <code>FieldPosition</code>. | ||
| 344 | * | ||
| 345 | * @since 1.4 | ||
| 346 | */ | ||
| 347 | public static class Field extends AttributedCharacterIterator.Attribute { | ||
| 348 | |||
| 349 | // Proclaim serial compatibility with 1.4 FCS | ||
| 350 | private static final long serialVersionUID = 276966692217360283L; | ||
| 351 | |||
| 352 | /** | ||
| 353 | * Creates a Field with the specified name. | ||
| 354 | * | ||
| 355 | * @param name Name of the attribute | ||
| 356 | */ | ||
| 357 | protected Field(String name) { | ||
| 358 | super(name); | ||
| 359 | } | ||
| 360 | } | ||
| 361 | |||
| 362 | |||
| 363 | /** | ||
| 364 | * FieldDelegate is notified by the various <code>Format</code> | ||
| 365 | * implementations as they are formatting the Objects. This allows for | ||
| 366 | * storage of the individual sections of the formatted String for | ||
| 367 | * later use, such as in a <code>FieldPosition</code> or for an | ||
| 368 | * <code>AttributedCharacterIterator</code>. | ||
| 369 | * <p> | ||
| 370 | * Delegates should NOT assume that the <code>Format</code> will notify | ||
| 371 | * the delegate of fields in any particular order. | ||
| 372 | * | ||
| 373 | * @see FieldPosition.Delegate | ||
| 374 | * @see CharacterIteratorFieldDelegate | ||
| 375 | */ | ||
| 376 | interface FieldDelegate { | ||
| 377 | /** | ||
| 378 | * Notified when a particular region of the String is formatted. This | ||
| 379 | * method will be invoked if there is no corresponding integer field id | ||
| 380 | * matching <code>attr</code>. | ||
| 381 | * | ||
| 382 | * @param attr Identifies the field matched | ||
| 383 | * @param value Value associated with the field | ||
| 384 | * @param start Beginning location of the field, will be >= 0 | ||
| 385 | * @param end End of the field, will be >= start and <= buffer.length() | ||
| 386 | * @param buffer Contains current formatted value, receiver should | ||
| 387 | * NOT modify it. | ||
| 388 | */ | ||
| 389 | public void formatted(Format.Field attr, Object value, int start, | ||
| 390 | int end, StringBuffer buffer); | ||
| 391 | |||
| 392 | /** | ||
| 393 | * Notified when a particular region of the String is formatted. | ||
| 394 | * | ||
| 395 | * @param fieldID Identifies the field by integer | ||
| 396 | * @param attr Identifies the field matched | ||
| 397 | * @param value Value associated with the field | ||
| 398 | * @param start Beginning location of the field, will be >= 0 | ||
| 399 | * @param end End of the field, will be >= start and <= buffer.length() | ||
| 400 | * @param buffer Contains current formatted value, receiver should | ||
| 401 | * NOT modify it. | ||
| 402 | */ | ||
| 403 | public void formatted(int fieldID, Format.Field attr, Object value, | ||
| 404 | int start, int end, StringBuffer buffer); | ||
| 405 | } | ||
| 406 | } | ||
Binary file not shown.
| @@ -0,0 +1,342 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 1999, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996, 1997 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.util.ArrayList; | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Utility class for normalizing and merging patterns for collation. | ||
| 45 | * Patterns are strings of the form <entry>*, where <entry> has the | ||
| 46 | * form: | ||
| 47 | * <pattern> := <entry>* | ||
| 48 | * <entry> := <separator><chars>{"/"<extension>} | ||
| 49 | * <separator> := "=", ",", ";", "<", "&" | ||
| 50 | * <chars>, and <extension> are both arbitrary strings. | ||
| 51 | * unquoted whitespaces are ignored. | ||
| 52 | * 'xxx' can be used to quote characters | ||
| 53 | * One difference from Collator is that & is used to reset to a current | ||
| 54 | * point. Or, in other words, it introduces a new sequence which is to | ||
| 55 | * be added to the old. | ||
| 56 | * That is: "a < b < c < d" is the same as "a < b & b < c & c < d" OR | ||
| 57 | * "a < b < d & b < c" | ||
| 58 | * XXX: make '' be a single quote. | ||
| 59 | * @see PatternEntry | ||
| 60 | * @author Mark Davis, Helena Shih | ||
| 61 | */ | ||
| 62 | |||
| 63 | final class MergeCollation { | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Creates from a pattern | ||
| 67 | * @exception ParseException If the input pattern is incorrect. | ||
| 68 | */ | ||
| 69 | public MergeCollation(String pattern) throws ParseException | ||
| 70 | { | ||
| 71 | for (int i = 0; i < statusArray.length; i++) | ||
| 72 | statusArray[i] = 0; | ||
| 73 | setPattern(pattern); | ||
| 74 | } | ||
| 75 | |||
| 76 | /** | ||
| 77 | * recovers current pattern | ||
| 78 | */ | ||
| 79 | public String getPattern() { | ||
| 80 | return getPattern(true); | ||
| 81 | } | ||
| 82 | |||
| 83 | /** | ||
| 84 | * recovers current pattern. | ||
| 85 | * @param withWhiteSpace puts spacing around the entries, and \n | ||
| 86 | * before & and < | ||
| 87 | */ | ||
| 88 | public String getPattern(boolean withWhiteSpace) { | ||
| 89 | StringBuffer result = new StringBuffer(); | ||
| 90 | PatternEntry tmp = null; | ||
| 91 | ArrayList extList = null; | ||
| 92 | int i; | ||
| 93 | for (i = 0; i < patterns.size(); ++i) { | ||
| 94 | PatternEntry entry = (PatternEntry) patterns.get(i); | ||
| 95 | if (entry.extension.length() != 0) { | ||
| 96 | if (extList == null) | ||
| 97 | extList = new ArrayList(); | ||
| 98 | extList.add(entry); | ||
| 99 | } else { | ||
| 100 | if (extList != null) { | ||
| 101 | PatternEntry last = findLastWithNoExtension(i-1); | ||
| 102 | for (int j = extList.size() - 1; j >= 0 ; j--) { | ||
| 103 | tmp = (PatternEntry)(extList.get(j)); | ||
| 104 | tmp.addToBuffer(result, false, withWhiteSpace, last); | ||
| 105 | } | ||
| 106 | extList = null; | ||
| 107 | } | ||
| 108 | entry.addToBuffer(result, false, withWhiteSpace, null); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | if (extList != null) { | ||
| 112 | PatternEntry last = findLastWithNoExtension(i-1); | ||
| 113 | for (int j = extList.size() - 1; j >= 0 ; j--) { | ||
| 114 | tmp = (PatternEntry)(extList.get(j)); | ||
| 115 | tmp.addToBuffer(result, false, withWhiteSpace, last); | ||
| 116 | } | ||
| 117 | extList = null; | ||
| 118 | } | ||
| 119 | return result.toString(); | ||
| 120 | } | ||
| 121 | |||
| 122 | private final PatternEntry findLastWithNoExtension(int i) { | ||
| 123 | for (--i;i >= 0; --i) { | ||
| 124 | PatternEntry entry = (PatternEntry) patterns.get(i); | ||
| 125 | if (entry.extension.length() == 0) { | ||
| 126 | return entry; | ||
| 127 | } | ||
| 128 | } | ||
| 129 | return null; | ||
| 130 | } | ||
| 131 | |||
| 132 | /** | ||
| 133 | * emits the pattern for collation builder. | ||
| 134 | * @return emits the string in the format understable to the collation | ||
| 135 | * builder. | ||
| 136 | */ | ||
| 137 | public String emitPattern() { | ||
| 138 | return emitPattern(true); | ||
| 139 | } | ||
| 140 | |||
| 141 | /** | ||
| 142 | * emits the pattern for collation builder. | ||
| 143 | * @param withWhiteSpace puts spacing around the entries, and \n | ||
| 144 | * before & and < | ||
| 145 | * @return emits the string in the format understable to the collation | ||
| 146 | * builder. | ||
| 147 | */ | ||
| 148 | public String emitPattern(boolean withWhiteSpace) { | ||
| 149 | StringBuffer result = new StringBuffer(); | ||
| 150 | for (int i = 0; i < patterns.size(); ++i) | ||
| 151 | { | ||
| 152 | PatternEntry entry = (PatternEntry) patterns.get(i); | ||
| 153 | if (entry != null) { | ||
| 154 | entry.addToBuffer(result, true, withWhiteSpace, null); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | return result.toString(); | ||
| 158 | } | ||
| 159 | |||
| 160 | /** | ||
| 161 | * sets the pattern. | ||
| 162 | */ | ||
| 163 | public void setPattern(String pattern) throws ParseException | ||
| 164 | { | ||
| 165 | patterns.clear(); | ||
| 166 | addPattern(pattern); | ||
| 167 | } | ||
| 168 | |||
| 169 | /** | ||
| 170 | * adds a pattern to the current one. | ||
| 171 | * @param pattern the new pattern to be added | ||
| 172 | */ | ||
| 173 | public void addPattern(String pattern) throws ParseException | ||
| 174 | { | ||
| 175 | if (pattern == null) | ||
| 176 | return; | ||
| 177 | |||
| 178 | PatternEntry.Parser parser = new PatternEntry.Parser(pattern); | ||
| 179 | |||
| 180 | PatternEntry entry = parser.next(); | ||
| 181 | while (entry != null) { | ||
| 182 | fixEntry(entry); | ||
| 183 | entry = parser.next(); | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | /** | ||
| 188 | * gets count of separate entries | ||
| 189 | * @return the size of pattern entries | ||
| 190 | */ | ||
| 191 | public int getCount() { | ||
| 192 | return patterns.size(); | ||
| 193 | } | ||
| 194 | |||
| 195 | /** | ||
| 196 | * gets count of separate entries | ||
| 197 | * @param index the offset of the desired pattern entry | ||
| 198 | * @return the requested pattern entry | ||
| 199 | */ | ||
| 200 | public PatternEntry getItemAt(int index) { | ||
| 201 | return (PatternEntry) patterns.get(index); | ||
| 202 | } | ||
| 203 | |||
| 204 | //============================================================ | ||
| 205 | // privates | ||
| 206 | //============================================================ | ||
| 207 | ArrayList patterns = new ArrayList(); // a list of PatternEntries | ||
| 208 | |||
| 209 | private transient PatternEntry saveEntry = null; | ||
| 210 | private transient PatternEntry lastEntry = null; | ||
| 211 | |||
| 212 | // This is really used as a local variable inside fixEntry, but we cache | ||
| 213 | // it here to avoid newing it up every time the method is called. | ||
| 214 | private transient StringBuffer excess = new StringBuffer(); | ||
| 215 | |||
| 216 | // | ||
| 217 | // When building a MergeCollation, we need to do lots of searches to see | ||
| 218 | // whether a given entry is already in the table. Since we're using an | ||
| 219 | // array, this would make the algorithm O(N*N). To speed things up, we | ||
| 220 | // use this bit array to remember whether the array contains any entries | ||
| 221 | // starting with each Unicode character. If not, we can avoid the search. | ||
| 222 | // Using BitSet would make this easier, but it's significantly slower. | ||
| 223 | // | ||
| 224 | private transient byte[] statusArray = new byte[8192]; | ||
| 225 | private final byte BITARRAYMASK = (byte)0x1; | ||
| 226 | private final int BYTEPOWER = 3; | ||
| 227 | private final int BYTEMASK = (1 << BYTEPOWER) - 1; | ||
| 228 | |||
| 229 | /* | ||
| 230 | If the strength is RESET, then just change the lastEntry to | ||
| 231 | be the current. (If the current is not in patterns, signal an error). | ||
| 232 | If not, then remove the current entry, and add it after lastEntry | ||
| 233 | (which is usually at the end). | ||
| 234 | */ | ||
| 235 | private final void fixEntry(PatternEntry newEntry) throws ParseException | ||
| 236 | { | ||
| 237 | // check to see whether the new entry has the same characters as the previous | ||
| 238 | // entry did (this can happen when a pattern declaring a difference between two | ||
| 239 | // strings that are canonically equivalent is normalized). If so, and the strength | ||
| 240 | // is anything other than IDENTICAL or RESET, throw an exception (you can't | ||
| 241 | // declare a string to be unequal to itself). --rtg 5/24/99 | ||
| 242 | if (lastEntry != null && newEntry.chars.equals(lastEntry.chars) | ||
| 243 | && newEntry.extension.equals(lastEntry.extension)) { | ||
| 244 | if (newEntry.strength != Collator.IDENTICAL | ||
| 245 | && newEntry.strength != PatternEntry.RESET) { | ||
| 246 | throw new ParseException("The entries " + lastEntry + " and " | ||
| 247 | + newEntry + " are adjacent in the rules, but have conflicting " | ||
| 248 | + "strengths: A character can't be unequal to itself.", -1); | ||
| 249 | } else { | ||
| 250 | // otherwise, just skip this entry and behave as though you never saw it | ||
| 251 | return; | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | boolean changeLastEntry = true; | ||
| 256 | if (newEntry.strength != PatternEntry.RESET) { | ||
| 257 | int oldIndex = -1; | ||
| 258 | |||
| 259 | if ((newEntry.chars.length() == 1)) { | ||
| 260 | |||
| 261 | char c = newEntry.chars.charAt(0); | ||
| 262 | int statusIndex = c >> BYTEPOWER; | ||
| 263 | byte bitClump = statusArray[statusIndex]; | ||
| 264 | byte setBit = (byte)(BITARRAYMASK << (c & BYTEMASK)); | ||
| 265 | |||
| 266 | if (bitClump != 0 && (bitClump & setBit) != 0) { | ||
| 267 | oldIndex = patterns.lastIndexOf(newEntry); | ||
| 268 | } else { | ||
| 269 | // We're going to add an element that starts with this | ||
| 270 | // character, so go ahead and set its bit. | ||
| 271 | statusArray[statusIndex] = (byte)(bitClump | setBit); | ||
| 272 | } | ||
| 273 | } else { | ||
| 274 | oldIndex = patterns.lastIndexOf(newEntry); | ||
| 275 | } | ||
| 276 | if (oldIndex != -1) { | ||
| 277 | patterns.remove(oldIndex); | ||
| 278 | } | ||
| 279 | |||
| 280 | excess.setLength(0); | ||
| 281 | int lastIndex = findLastEntry(lastEntry, excess); | ||
| 282 | |||
| 283 | if (excess.length() != 0) { | ||
| 284 | newEntry.extension = excess + newEntry.extension; | ||
| 285 | if (lastIndex != patterns.size()) { | ||
| 286 | lastEntry = saveEntry; | ||
| 287 | changeLastEntry = false; | ||
| 288 | } | ||
| 289 | } | ||
| 290 | if (lastIndex == patterns.size()) { | ||
| 291 | patterns.add(newEntry); | ||
| 292 | saveEntry = newEntry; | ||
| 293 | } else { | ||
| 294 | patterns.add(lastIndex, newEntry); | ||
| 295 | } | ||
| 296 | } | ||
| 297 | if (changeLastEntry) { | ||
| 298 | lastEntry = newEntry; | ||
| 299 | } | ||
| 300 | } | ||
| 301 | |||
| 302 | private final int findLastEntry(PatternEntry entry, | ||
| 303 | StringBuffer excessChars) throws ParseException | ||
| 304 | { | ||
| 305 | if (entry == null) | ||
| 306 | return 0; | ||
| 307 | |||
| 308 | if (entry.strength != PatternEntry.RESET) { | ||
| 309 | // Search backwards for string that contains this one; | ||
| 310 | // most likely entry is last one | ||
| 311 | |||
| 312 | int oldIndex = -1; | ||
| 313 | if ((entry.chars.length() == 1)) { | ||
| 314 | int index = entry.chars.charAt(0) >> BYTEPOWER; | ||
| 315 | if ((statusArray[index] & | ||
| 316 | (BITARRAYMASK << (entry.chars.charAt(0) & BYTEMASK))) != 0) { | ||
| 317 | oldIndex = patterns.lastIndexOf(entry); | ||
| 318 | } | ||
| 319 | } else { | ||
| 320 | oldIndex = patterns.lastIndexOf(entry); | ||
| 321 | } | ||
| 322 | if ((oldIndex == -1)) | ||
| 323 | throw new ParseException("couldn't find last entry: " | ||
| 324 | + entry, oldIndex); | ||
| 325 | return oldIndex + 1; | ||
| 326 | } else { | ||
| 327 | int i; | ||
| 328 | for (i = patterns.size() - 1; i >= 0; --i) { | ||
| 329 | PatternEntry e = (PatternEntry) patterns.get(i); | ||
| 330 | if (e.chars.regionMatches(0,entry.chars,0, | ||
| 331 | e.chars.length())) { | ||
| 332 | excessChars.append(entry.chars.substring(e.chars.length(), | ||
| 333 | entry.chars.length())); | ||
| 334 | break; | ||
| 335 | } | ||
| 336 | } | ||
| 337 | if (i == -1) | ||
| 338 | throw new ParseException("couldn't find: " + entry, i); | ||
| 339 | return i + 1; | ||
| 340 | } | ||
| 341 | } | ||
| 342 | } | ||
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,1594 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.io.InvalidObjectException; | ||
| 42 | import java.io.IOException; | ||
| 43 | import java.io.ObjectInputStream; | ||
| 44 | import java.text.DecimalFormat; | ||
| 45 | import java.util.ArrayList; | ||
| 46 | import java.util.Arrays; | ||
| 47 | import java.util.Date; | ||
| 48 | import java.util.List; | ||
| 49 | import java.util.Locale; | ||
| 50 | |||
| 51 | |||
| 52 | /** | ||
| 53 | * <code>MessageFormat</code> provides a means to produce concatenated | ||
| 54 | * messages in a language-neutral way. Use this to construct messages | ||
| 55 | * displayed for end users. | ||
| 56 | * | ||
| 57 | * <p> | ||
| 58 | * <code>MessageFormat</code> takes a set of objects, formats them, then | ||
| 59 | * inserts the formatted strings into the pattern at the appropriate places. | ||
| 60 | * | ||
| 61 | * <p> | ||
| 62 | * <strong>Note:</strong> | ||
| 63 | * <code>MessageFormat</code> differs from the other <code>Format</code> | ||
| 64 | * classes in that you create a <code>MessageFormat</code> object with one | ||
| 65 | * of its constructors (not with a <code>getInstance</code> style factory | ||
| 66 | * method). The factory methods aren't necessary because <code>MessageFormat</code> | ||
| 67 | * itself doesn't implement locale specific behavior. Any locale specific | ||
| 68 | * behavior is defined by the pattern that you provide as well as the | ||
| 69 | * subformats used for inserted arguments. | ||
| 70 | * | ||
| 71 | * <h4><a name="patterns">Patterns and Their Interpretation</a></h4> | ||
| 72 | * | ||
| 73 | * <code>MessageFormat</code> uses patterns of the following form: | ||
| 74 | * <blockquote><pre> | ||
| 75 | * <i>MessageFormatPattern:</i> | ||
| 76 | * <i>String</i> | ||
| 77 | * <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i> | ||
| 78 | * | ||
| 79 | * <i>FormatElement:</i> | ||
| 80 | * { <i>ArgumentIndex</i> } | ||
| 81 | * { <i>ArgumentIndex</i> , <i>FormatType</i> } | ||
| 82 | * { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> } | ||
| 83 | * | ||
| 84 | * <i>FormatType: one of </i> | ||
| 85 | * number date time choice | ||
| 86 | * | ||
| 87 | * <i>FormatStyle:</i> | ||
| 88 | * short | ||
| 89 | * medium | ||
| 90 | * long | ||
| 91 | * full | ||
| 92 | * integer | ||
| 93 | * currency | ||
| 94 | * percent | ||
| 95 | * <i>SubformatPattern</i> | ||
| 96 | * </pre></blockquote> | ||
| 97 | * | ||
| 98 | * <p>Within a <i>String</i>, a pair of single quotes can be used to | ||
| 99 | * quote any arbitrary characters except single quotes. For example, | ||
| 100 | * pattern string <code>"'{0}'"</code> represents string | ||
| 101 | * <code>"{0}"</code>, not a <i>FormatElement</i>. A single quote itself | ||
| 102 | * must be represented by doubled single quotes {@code ''} throughout a | ||
| 103 | * <i>String</i>. For example, pattern string <code>"'{''}'"</code> is | ||
| 104 | * interpreted as a sequence of <code>'{</code> (start of quoting and a | ||
| 105 | * left curly brace), <code>''</code> (a single quote), and | ||
| 106 | * <code>}'</code> (a right curly brace and end of quoting), | ||
| 107 | * <em>not</em> <code>'{'</code> and <code>'}'</code> (quoted left and | ||
| 108 | * right curly braces): representing string <code>"{'}"</code>, | ||
| 109 | * <em>not</em> <code>"{}"</code>. | ||
| 110 | * | ||
| 111 | * <p>A <i>SubformatPattern</i> is interpreted by its corresponding | ||
| 112 | * subformat, and subformat-dependent pattern rules apply. For example, | ||
| 113 | * pattern string <code>"{1,number,<u>$'#',##</u>}"</code> | ||
| 114 | * (<i>SubformatPattern</i> with underline) will produce a number format | ||
| 115 | * with the pound-sign quoted, with a result such as: {@code | ||
| 116 | * "$#31,45"}. Refer to each {@code Format} subclass documentation for | ||
| 117 | * details. | ||
| 118 | * | ||
| 119 | * <p>Any unmatched quote is treated as closed at the end of the given | ||
| 120 | * pattern. For example, pattern string {@code "'{0}"} is treated as | ||
| 121 | * pattern {@code "'{0}'"}. | ||
| 122 | * | ||
| 123 | * <p>Any curly braces within an unquoted pattern must be balanced. For | ||
| 124 | * example, <code>"ab {0} de"</code> and <code>"ab '}' de"</code> are | ||
| 125 | * valid patterns, but <code>"ab {0'}' de"</code>, <code>"ab } de"</code> | ||
| 126 | * and <code>"''{''"</code> are not. | ||
| 127 | * | ||
| 128 | * <p> | ||
| 129 | * <dl><dt><b>Warning:</b><dd>The rules for using quotes within message | ||
| 130 | * format patterns unfortunately have shown to be somewhat confusing. | ||
| 131 | * In particular, it isn't always obvious to localizers whether single | ||
| 132 | * quotes need to be doubled or not. Make sure to inform localizers about | ||
| 133 | * the rules, and tell them (for example, by using comments in resource | ||
| 134 | * bundle source files) which strings will be processed by {@code MessageFormat}. | ||
| 135 | * Note that localizers may need to use single quotes in translated | ||
| 136 | * strings where the original version doesn't have them. | ||
| 137 | * </dl> | ||
| 138 | * <p> | ||
| 139 | * The <i>ArgumentIndex</i> value is a non-negative integer written | ||
| 140 | * using the digits {@code '0'} through {@code '9'}, and represents an index into the | ||
| 141 | * {@code arguments} array passed to the {@code format} methods | ||
| 142 | * or the result array returned by the {@code parse} methods. | ||
| 143 | * <p> | ||
| 144 | * The <i>FormatType</i> and <i>FormatStyle</i> values are used to create | ||
| 145 | * a {@code Format} instance for the format element. The following | ||
| 146 | * table shows how the values map to {@code Format} instances. Combinations not | ||
| 147 | * shown in the table are illegal. A <i>SubformatPattern</i> must | ||
| 148 | * be a valid pattern string for the {@code Format} subclass used. | ||
| 149 | * <p> | ||
| 150 | * <table border=1 summary="Shows how FormatType and FormatStyle values map to Format instances"> | ||
| 151 | * <tr> | ||
| 152 | * <th id="ft" class="TableHeadingColor">FormatType | ||
| 153 | * <th id="fs" class="TableHeadingColor">FormatStyle | ||
| 154 | * <th id="sc" class="TableHeadingColor">Subformat Created | ||
| 155 | * <tr> | ||
| 156 | * <td headers="ft"><i>(none)</i> | ||
| 157 | * <td headers="fs"><i>(none)</i> | ||
| 158 | * <td headers="sc"><code>null</code> | ||
| 159 | * <tr> | ||
| 160 | * <td headers="ft" rowspan=5><code>number</code> | ||
| 161 | * <td headers="fs"><i>(none)</i> | ||
| 162 | * <td headers="sc">{@link NumberFormat#getInstance(Locale) NumberFormat.getInstance}{@code (getLocale())} | ||
| 163 | * <tr> | ||
| 164 | * <td headers="fs"><code>integer</code> | ||
| 165 | * <td headers="sc">{@link NumberFormat#getIntegerInstance(Locale) NumberFormat.getIntegerInstance}{@code (getLocale())} | ||
| 166 | * <tr> | ||
| 167 | * <td headers="fs"><code>currency</code> | ||
| 168 | * <td headers="sc">{@link NumberFormat#getCurrencyInstance(Locale) NumberFormat.getCurrencyInstance}{@code (getLocale())} | ||
| 169 | * <tr> | ||
| 170 | * <td headers="fs"><code>percent</code> | ||
| 171 | * <td headers="sc">{@link NumberFormat#getPercentInstance(Locale) NumberFormat.getPercentInstance}{@code (getLocale())} | ||
| 172 | * <tr> | ||
| 173 | * <td headers="fs"><i>SubformatPattern</i> | ||
| 174 | * <td headers="sc">{@code new} {@link DecimalFormat#DecimalFormat(String,DecimalFormatSymbols) DecimalFormat}{@code (subformatPattern,} {@link DecimalFormatSymbols#getInstance(Locale) DecimalFormatSymbols.getInstance}{@code (getLocale()))} | ||
| 175 | * <tr> | ||
| 176 | * <td headers="ft" rowspan=6><code>date</code> | ||
| 177 | * <td headers="fs"><i>(none)</i> | ||
| 178 | * <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} | ||
| 179 | * <tr> | ||
| 180 | * <td headers="fs"><code>short</code> | ||
| 181 | * <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())} | ||
| 182 | * <tr> | ||
| 183 | * <td headers="fs"><code>medium</code> | ||
| 184 | * <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} | ||
| 185 | * <tr> | ||
| 186 | * <td headers="fs"><code>long</code> | ||
| 187 | * <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())} | ||
| 188 | * <tr> | ||
| 189 | * <td headers="fs"><code>full</code> | ||
| 190 | * <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())} | ||
| 191 | * <tr> | ||
| 192 | * <td headers="fs"><i>SubformatPattern</i> | ||
| 193 | * <td headers="sc">{@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())} | ||
| 194 | * <tr> | ||
| 195 | * <td headers="ft" rowspan=6><code>time</code> | ||
| 196 | * <td headers="fs"><i>(none)</i> | ||
| 197 | * <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} | ||
| 198 | * <tr> | ||
| 199 | * <td headers="fs"><code>short</code> | ||
| 200 | * <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())} | ||
| 201 | * <tr> | ||
| 202 | * <td headers="fs"><code>medium</code> | ||
| 203 | * <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} | ||
| 204 | * <tr> | ||
| 205 | * <td headers="fs"><code>long</code> | ||
| 206 | * <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())} | ||
| 207 | * <tr> | ||
| 208 | * <td headers="fs"><code>full</code> | ||
| 209 | * <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())} | ||
| 210 | * <tr> | ||
| 211 | * <td headers="fs"><i>SubformatPattern</i> | ||
| 212 | * <td headers="sc">{@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())} | ||
| 213 | * <tr> | ||
| 214 | * <td headers="ft"><code>choice</code> | ||
| 215 | * <td headers="fs"><i>SubformatPattern</i> | ||
| 216 | * <td headers="sc">{@code new} {@link ChoiceFormat#ChoiceFormat(String) ChoiceFormat}{@code (subformatPattern)} | ||
| 217 | * </table> | ||
| 218 | * <p> | ||
| 219 | * | ||
| 220 | * <h4>Usage Information</h4> | ||
| 221 | * | ||
| 222 | * <p> | ||
| 223 | * Here are some examples of usage. | ||
| 224 | * In real internationalized programs, the message format pattern and other | ||
| 225 | * static strings will, of course, be obtained from resource bundles. | ||
| 226 | * Other parameters will be dynamically determined at runtime. | ||
| 227 | * <p> | ||
| 228 | * The first example uses the static method <code>MessageFormat.format</code>, | ||
| 229 | * which internally creates a <code>MessageFormat</code> for one-time use: | ||
| 230 | * <blockquote><pre> | ||
| 231 | * int planet = 7; | ||
| 232 | * String event = "a disturbance in the Force"; | ||
| 233 | * | ||
| 234 | * String result = MessageFormat.format( | ||
| 235 | * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.", | ||
| 236 | * planet, new Date(), event); | ||
| 237 | * </pre></blockquote> | ||
| 238 | * The output is: | ||
| 239 | * <blockquote><pre> | ||
| 240 | * At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7. | ||
| 241 | * </pre></blockquote> | ||
| 242 | * | ||
| 243 | * <p> | ||
| 244 | * The following example creates a <code>MessageFormat</code> instance that | ||
| 245 | * can be used repeatedly: | ||
| 246 | * <blockquote><pre> | ||
| 247 | * int fileCount = 1273; | ||
| 248 | * String diskName = "MyDisk"; | ||
| 249 | * Object[] testArgs = {new Long(fileCount), diskName}; | ||
| 250 | * | ||
| 251 | * MessageFormat form = new MessageFormat( | ||
| 252 | * "The disk \"{1}\" contains {0} file(s)."); | ||
| 253 | * | ||
| 254 | * System.out.println(form.format(testArgs)); | ||
| 255 | * </pre></blockquote> | ||
| 256 | * The output with different values for <code>fileCount</code>: | ||
| 257 | * <blockquote><pre> | ||
| 258 | * The disk "MyDisk" contains 0 file(s). | ||
| 259 | * The disk "MyDisk" contains 1 file(s). | ||
| 260 | * The disk "MyDisk" contains 1,273 file(s). | ||
| 261 | * </pre></blockquote> | ||
| 262 | * | ||
| 263 | * <p> | ||
| 264 | * For more sophisticated patterns, you can use a <code>ChoiceFormat</code> | ||
| 265 | * to produce correct forms for singular and plural: | ||
| 266 | * <blockquote><pre> | ||
| 267 | * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}."); | ||
| 268 | * double[] filelimits = {0,1,2}; | ||
| 269 | * String[] filepart = {"no files","one file","{0,number} files"}; | ||
| 270 | * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart); | ||
| 271 | * form.setFormatByArgumentIndex(0, fileform); | ||
| 272 | * | ||
| 273 | * int fileCount = 1273; | ||
| 274 | * String diskName = "MyDisk"; | ||
| 275 | * Object[] testArgs = {new Long(fileCount), diskName}; | ||
| 276 | * | ||
| 277 | * System.out.println(form.format(testArgs)); | ||
| 278 | * </pre></blockquote> | ||
| 279 | * The output with different values for <code>fileCount</code>: | ||
| 280 | * <blockquote><pre> | ||
| 281 | * The disk "MyDisk" contains no files. | ||
| 282 | * The disk "MyDisk" contains one file. | ||
| 283 | * The disk "MyDisk" contains 1,273 files. | ||
| 284 | * </pre></blockquote> | ||
| 285 | * | ||
| 286 | * <p> | ||
| 287 | * You can create the <code>ChoiceFormat</code> programmatically, as in the | ||
| 288 | * above example, or by using a pattern. See {@link ChoiceFormat} | ||
| 289 | * for more information. | ||
| 290 | * <blockquote><pre> | ||
| 291 | * form.applyPattern( | ||
| 292 | * "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}."); | ||
| 293 | * </pre></blockquote> | ||
| 294 | * | ||
| 295 | * <p> | ||
| 296 | * <strong>Note:</strong> As we see above, the string produced | ||
| 297 | * by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated as special; | ||
| 298 | * occurrences of '{' are used to indicate subformats, and cause recursion. | ||
| 299 | * If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code> | ||
| 300 | * programmatically (instead of using the string patterns), then be careful not to | ||
| 301 | * produce a format that recurses on itself, which will cause an infinite loop. | ||
| 302 | * <p> | ||
| 303 | * When a single argument is parsed more than once in the string, the last match | ||
| 304 | * will be the final result of the parsing. For example, | ||
| 305 | * <blockquote><pre> | ||
| 306 | * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}"); | ||
| 307 | * Object[] objs = {new Double(3.1415)}; | ||
| 308 | * String result = mf.format( objs ); | ||
| 309 | * // result now equals "3.14, 3.1" | ||
| 310 | * objs = null; | ||
| 311 | * objs = mf.parse(result, new ParsePosition(0)); | ||
| 312 | * // objs now equals {new Double(3.1)} | ||
| 313 | * </pre></blockquote> | ||
| 314 | * | ||
| 315 | * <p> | ||
| 316 | * Likewise, parsing with a {@code MessageFormat} object using patterns containing | ||
| 317 | * multiple occurrences of the same argument would return the last match. For | ||
| 318 | * example, | ||
| 319 | * <blockquote><pre> | ||
| 320 | * MessageFormat mf = new MessageFormat("{0}, {0}, {0}"); | ||
| 321 | * String forParsing = "x, y, z"; | ||
| 322 | * Object[] objs = mf.parse(forParsing, new ParsePosition(0)); | ||
| 323 | * // result now equals {new String("z")} | ||
| 324 | * </pre></blockquote> | ||
| 325 | * | ||
| 326 | * <h4><a name="synchronization">Synchronization</a></h4> | ||
| 327 | * | ||
| 328 | * <p> | ||
| 329 | * Message formats are not synchronized. | ||
| 330 | * It is recommended to create separate format instances for each thread. | ||
| 331 | * If multiple threads access a format concurrently, it must be synchronized | ||
| 332 | * externally. | ||
| 333 | * | ||
| 334 | * @see java.util.Locale | ||
| 335 | * @see Format | ||
| 336 | * @see NumberFormat | ||
| 337 | * @see DecimalFormat | ||
| 338 | * @see DecimalFormatSymbols | ||
| 339 | * @see ChoiceFormat | ||
| 340 | * @see DateFormat | ||
| 341 | * @see SimpleDateFormat | ||
| 342 | * | ||
| 343 | * @author Mark Davis | ||
| 344 | */ | ||
| 345 | |||
| 346 | public class MessageFormat extends Format { | ||
| 347 | |||
| 348 | private static final long serialVersionUID = 6479157306784022952L; | ||
| 349 | |||
| 350 | /** | ||
| 351 | * Constructs a MessageFormat for the default locale and the | ||
| 352 | * specified pattern. | ||
| 353 | * The constructor first sets the locale, then parses the pattern and | ||
| 354 | * creates a list of subformats for the format elements contained in it. | ||
| 355 | * Patterns and their interpretation are specified in the | ||
| 356 | * <a href="#patterns">class description</a>. | ||
| 357 | * | ||
| 358 | * @param pattern the pattern for this message format | ||
| 359 | * @exception IllegalArgumentException if the pattern is invalid | ||
| 360 | */ | ||
| 361 | public MessageFormat(String pattern) { | ||
| 362 | this.locale = Locale.getDefault(Locale.Category.FORMAT); | ||
| 363 | applyPattern(pattern); | ||
| 364 | } | ||
| 365 | |||
| 366 | /** | ||
| 367 | * Constructs a MessageFormat for the specified locale and | ||
| 368 | * pattern. | ||
| 369 | * The constructor first sets the locale, then parses the pattern and | ||
| 370 | * creates a list of subformats for the format elements contained in it. | ||
| 371 | * Patterns and their interpretation are specified in the | ||
| 372 | * <a href="#patterns">class description</a>. | ||
| 373 | * | ||
| 374 | * @param pattern the pattern for this message format | ||
| 375 | * @param locale the locale for this message format | ||
| 376 | * @exception IllegalArgumentException if the pattern is invalid | ||
| 377 | * @since 1.4 | ||
| 378 | */ | ||
| 379 | public MessageFormat(String pattern, Locale locale) { | ||
| 380 | this.locale = locale; | ||
| 381 | applyPattern(pattern); | ||
| 382 | } | ||
| 383 | |||
| 384 | /** | ||
| 385 | * Sets the locale to be used when creating or comparing subformats. | ||
| 386 | * This affects subsequent calls | ||
| 387 | * <ul> | ||
| 388 | * <li>to the {@link #applyPattern applyPattern} | ||
| 389 | * and {@link #toPattern toPattern} methods if format elements specify | ||
| 390 | * a format type and therefore have the subformats created in the | ||
| 391 | * <code>applyPattern</code> method, as well as | ||
| 392 | * <li>to the <code>format</code> and | ||
| 393 | * {@link #formatToCharacterIterator formatToCharacterIterator} methods | ||
| 394 | * if format elements do not specify a format type and therefore have | ||
| 395 | * the subformats created in the formatting methods. | ||
| 396 | * </ul> | ||
| 397 | * Subformats that have already been created are not affected. | ||
| 398 | * | ||
| 399 | * @param locale the locale to be used when creating or comparing subformats | ||
| 400 | */ | ||
| 401 | public void setLocale(Locale locale) { | ||
| 402 | this.locale = locale; | ||
| 403 | } | ||
| 404 | |||
| 405 | /** | ||
| 406 | * Gets the locale that's used when creating or comparing subformats. | ||
| 407 | * | ||
| 408 | * @return the locale used when creating or comparing subformats | ||
| 409 | */ | ||
| 410 | public Locale getLocale() { | ||
| 411 | return locale; | ||
| 412 | } | ||
| 413 | |||
| 414 | |||
| 415 | /** | ||
| 416 | * Sets the pattern used by this message format. | ||
| 417 | * The method parses the pattern and creates a list of subformats | ||
| 418 | * for the format elements contained in it. | ||
| 419 | * Patterns and their interpretation are specified in the | ||
| 420 | * <a href="#patterns">class description</a>. | ||
| 421 | * | ||
| 422 | * @param pattern the pattern for this message format | ||
| 423 | * @exception IllegalArgumentException if the pattern is invalid | ||
| 424 | */ | ||
| 425 | public void applyPattern(String pattern) { | ||
| 426 | StringBuilder[] segments = new StringBuilder[4]; | ||
| 427 | // Allocate only segments[SEG_RAW] here. The rest are | ||
| 428 | // allocated on demand. | ||
| 429 | segments[SEG_RAW] = new StringBuilder(); | ||
| 430 | |||
| 431 | int part = SEG_RAW; | ||
| 432 | int formatNumber = 0; | ||
| 433 | boolean inQuote = false; | ||
| 434 | int braceStack = 0; | ||
| 435 | maxOffset = -1; | ||
| 436 | for (int i = 0; i < pattern.length(); ++i) { | ||
| 437 | char ch = pattern.charAt(i); | ||
| 438 | if (part == SEG_RAW) { | ||
| 439 | if (ch == '\'') { | ||
| 440 | if (i + 1 < pattern.length() | ||
| 441 | && pattern.charAt(i+1) == '\'') { | ||
| 442 | segments[part].append(ch); // handle doubles | ||
| 443 | ++i; | ||
| 444 | } else { | ||
| 445 | inQuote = !inQuote; | ||
| 446 | } | ||
| 447 | } else if (ch == '{' && !inQuote) { | ||
| 448 | part = SEG_INDEX; | ||
| 449 | if (segments[SEG_INDEX] == null) { | ||
| 450 | segments[SEG_INDEX] = new StringBuilder(); | ||
| 451 | } | ||
| 452 | } else { | ||
| 453 | segments[part].append(ch); | ||
| 454 | } | ||
| 455 | } else { | ||
| 456 | if (inQuote) { // just copy quotes in parts | ||
| 457 | segments[part].append(ch); | ||
| 458 | if (ch == '\'') { | ||
| 459 | inQuote = false; | ||
| 460 | } | ||
| 461 | } else { | ||
| 462 | switch (ch) { | ||
| 463 | case ',': | ||
| 464 | if (part < SEG_MODIFIER) { | ||
| 465 | if (segments[++part] == null) { | ||
| 466 | segments[part] = new StringBuilder(); | ||
| 467 | } | ||
| 468 | } else { | ||
| 469 | segments[part].append(ch); | ||
| 470 | } | ||
| 471 | break; | ||
| 472 | case '{': | ||
| 473 | ++braceStack; | ||
| 474 | segments[part].append(ch); | ||
| 475 | break; | ||
| 476 | case '}': | ||
| 477 | if (braceStack == 0) { | ||
| 478 | part = SEG_RAW; | ||
| 479 | makeFormat(i, formatNumber, segments); | ||
| 480 | formatNumber++; | ||
| 481 | // throw away other segments | ||
| 482 | segments[SEG_INDEX] = null; | ||
| 483 | segments[SEG_TYPE] = null; | ||
| 484 | segments[SEG_MODIFIER] = null; | ||
| 485 | } else { | ||
| 486 | --braceStack; | ||
| 487 | segments[part].append(ch); | ||
| 488 | } | ||
| 489 | break; | ||
| 490 | case ' ': | ||
| 491 | // Skip any leading space chars for SEG_TYPE. | ||
| 492 | if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) { | ||
| 493 | segments[part].append(ch); | ||
| 494 | } | ||
| 495 | break; | ||
| 496 | case '\'': | ||
| 497 | inQuote = true; | ||
| 498 | // fall through, so we keep quotes in other parts | ||
| 499 | default: | ||
| 500 | segments[part].append(ch); | ||
| 501 | break; | ||
| 502 | } | ||
| 503 | } | ||
| 504 | } | ||
| 505 | } | ||
| 506 | if (braceStack == 0 && part != 0) { | ||
| 507 | maxOffset = -1; | ||
| 508 | throw new IllegalArgumentException("Unmatched braces in the pattern."); | ||
| 509 | } | ||
| 510 | this.pattern = segments[0].toString(); | ||
| 511 | } | ||
| 512 | |||
| 513 | |||
| 514 | /** | ||
| 515 | * Returns a pattern representing the current state of the message format. | ||
| 516 | * The string is constructed from internal information and therefore | ||
| 517 | * does not necessarily equal the previously applied pattern. | ||
| 518 | * | ||
| 519 | * @return a pattern representing the current state of the message format | ||
| 520 | */ | ||
| 521 | public String toPattern() { | ||
| 522 | // later, make this more extensible | ||
| 523 | int lastOffset = 0; | ||
| 524 | StringBuilder result = new StringBuilder(); | ||
| 525 | for (int i = 0; i <= maxOffset; ++i) { | ||
| 526 | copyAndFixQuotes(pattern, lastOffset, offsets[i], result); | ||
| 527 | lastOffset = offsets[i]; | ||
| 528 | result.append('{').append(argumentNumbers[i]); | ||
| 529 | Format fmt = formats[i]; | ||
| 530 | if (fmt == null) { | ||
| 531 | // do nothing, string format | ||
| 532 | } else if (fmt instanceof NumberFormat) { | ||
| 533 | if (fmt.equals(NumberFormat.getInstance(locale))) { | ||
| 534 | result.append(",number"); | ||
| 535 | } else if (fmt.equals(NumberFormat.getCurrencyInstance(locale))) { | ||
| 536 | result.append(",number,currency"); | ||
| 537 | } else if (fmt.equals(NumberFormat.getPercentInstance(locale))) { | ||
| 538 | result.append(",number,percent"); | ||
| 539 | } else if (fmt.equals(NumberFormat.getIntegerInstance(locale))) { | ||
| 540 | result.append(",number,integer"); | ||
| 541 | } else { | ||
| 542 | if (fmt instanceof DecimalFormat) { | ||
| 543 | result.append(",number,").append(((DecimalFormat)fmt).toPattern()); | ||
| 544 | } else if (fmt instanceof ChoiceFormat) { | ||
| 545 | result.append(",choice,").append(((ChoiceFormat)fmt).toPattern()); | ||
| 546 | } else { | ||
| 547 | // UNKNOWN | ||
| 548 | } | ||
| 549 | } | ||
| 550 | } else if (fmt instanceof DateFormat) { | ||
| 551 | int index; | ||
| 552 | for (index = MODIFIER_DEFAULT; index < DATE_TIME_MODIFIERS.length; index++) { | ||
| 553 | DateFormat df = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[index], | ||
| 554 | locale); | ||
| 555 | if (fmt.equals(df)) { | ||
| 556 | result.append(",date"); | ||
| 557 | break; | ||
| 558 | } | ||
| 559 | df = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[index], | ||
| 560 | locale); | ||
| 561 | if (fmt.equals(df)) { | ||
| 562 | result.append(",time"); | ||
| 563 | break; | ||
| 564 | } | ||
| 565 | } | ||
| 566 | if (index >= DATE_TIME_MODIFIERS.length) { | ||
| 567 | if (fmt instanceof SimpleDateFormat) { | ||
| 568 | result.append(",date,").append(((SimpleDateFormat)fmt).toPattern()); | ||
| 569 | } else { | ||
| 570 | // UNKNOWN | ||
| 571 | } | ||
| 572 | } else if (index != MODIFIER_DEFAULT) { | ||
| 573 | result.append(',').append(DATE_TIME_MODIFIER_KEYWORDS[index]); | ||
| 574 | } | ||
| 575 | } else { | ||
| 576 | //result.append(", unknown"); | ||
| 577 | } | ||
| 578 | result.append('}'); | ||
| 579 | } | ||
| 580 | copyAndFixQuotes(pattern, lastOffset, pattern.length(), result); | ||
| 581 | return result.toString(); | ||
| 582 | } | ||
| 583 | |||
| 584 | /** | ||
| 585 | * Sets the formats to use for the values passed into | ||
| 586 | * <code>format</code> methods or returned from <code>parse</code> | ||
| 587 | * methods. The indices of elements in <code>newFormats</code> | ||
| 588 | * correspond to the argument indices used in the previously set | ||
| 589 | * pattern string. | ||
| 590 | * The order of formats in <code>newFormats</code> thus corresponds to | ||
| 591 | * the order of elements in the <code>arguments</code> array passed | ||
| 592 | * to the <code>format</code> methods or the result array returned | ||
| 593 | * by the <code>parse</code> methods. | ||
| 594 | * <p> | ||
| 595 | * If an argument index is used for more than one format element | ||
| 596 | * in the pattern string, then the corresponding new format is used | ||
| 597 | * for all such format elements. If an argument index is not used | ||
| 598 | * for any format element in the pattern string, then the | ||
| 599 | * corresponding new format is ignored. If fewer formats are provided | ||
| 600 | * than needed, then only the formats for argument indices less | ||
| 601 | * than <code>newFormats.length</code> are replaced. | ||
| 602 | * | ||
| 603 | * @param newFormats the new formats to use | ||
| 604 | * @exception NullPointerException if <code>newFormats</code> is null | ||
| 605 | * @since 1.4 | ||
| 606 | */ | ||
| 607 | public void setFormatsByArgumentIndex(Format[] newFormats) { | ||
| 608 | for (int i = 0; i <= maxOffset; i++) { | ||
| 609 | int j = argumentNumbers[i]; | ||
| 610 | if (j < newFormats.length) { | ||
| 611 | formats[i] = newFormats[j]; | ||
| 612 | } | ||
| 613 | } | ||
| 614 | } | ||
| 615 | |||
| 616 | /** | ||
| 617 | * Sets the formats to use for the format elements in the | ||
| 618 | * previously set pattern string. | ||
| 619 | * The order of formats in <code>newFormats</code> corresponds to | ||
| 620 | * the order of format elements in the pattern string. | ||
| 621 | * <p> | ||
| 622 | * If more formats are provided than needed by the pattern string, | ||
| 623 | * the remaining ones are ignored. If fewer formats are provided | ||
| 624 | * than needed, then only the first <code>newFormats.length</code> | ||
| 625 | * formats are replaced. | ||
| 626 | * <p> | ||
| 627 | * Since the order of format elements in a pattern string often | ||
| 628 | * changes during localization, it is generally better to use the | ||
| 629 | * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex} | ||
| 630 | * method, which assumes an order of formats corresponding to the | ||
| 631 | * order of elements in the <code>arguments</code> array passed to | ||
| 632 | * the <code>format</code> methods or the result array returned by | ||
| 633 | * the <code>parse</code> methods. | ||
| 634 | * | ||
| 635 | * @param newFormats the new formats to use | ||
| 636 | * @exception NullPointerException if <code>newFormats</code> is null | ||
| 637 | */ | ||
| 638 | public void setFormats(Format[] newFormats) { | ||
| 639 | int runsToCopy = newFormats.length; | ||
| 640 | if (runsToCopy > maxOffset + 1) { | ||
| 641 | runsToCopy = maxOffset + 1; | ||
| 642 | } | ||
| 643 | for (int i = 0; i < runsToCopy; i++) { | ||
| 644 | formats[i] = newFormats[i]; | ||
| 645 | } | ||
| 646 | } | ||
| 647 | |||
| 648 | /** | ||
| 649 | * Sets the format to use for the format elements within the | ||
| 650 | * previously set pattern string that use the given argument | ||
| 651 | * index. | ||
| 652 | * The argument index is part of the format element definition and | ||
| 653 | * represents an index into the <code>arguments</code> array passed | ||
| 654 | * to the <code>format</code> methods or the result array returned | ||
| 655 | * by the <code>parse</code> methods. | ||
| 656 | * <p> | ||
| 657 | * If the argument index is used for more than one format element | ||
| 658 | * in the pattern string, then the new format is used for all such | ||
| 659 | * format elements. If the argument index is not used for any format | ||
| 660 | * element in the pattern string, then the new format is ignored. | ||
| 661 | * | ||
| 662 | * @param argumentIndex the argument index for which to use the new format | ||
| 663 | * @param newFormat the new format to use | ||
| 664 | * @since 1.4 | ||
| 665 | */ | ||
| 666 | public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) { | ||
| 667 | for (int j = 0; j <= maxOffset; j++) { | ||
| 668 | if (argumentNumbers[j] == argumentIndex) { | ||
| 669 | formats[j] = newFormat; | ||
| 670 | } | ||
| 671 | } | ||
| 672 | } | ||
| 673 | |||
| 674 | /** | ||
| 675 | * Sets the format to use for the format element with the given | ||
| 676 | * format element index within the previously set pattern string. | ||
| 677 | * The format element index is the zero-based number of the format | ||
| 678 | * element counting from the start of the pattern string. | ||
| 679 | * <p> | ||
| 680 | * Since the order of format elements in a pattern string often | ||
| 681 | * changes during localization, it is generally better to use the | ||
| 682 | * {@link #setFormatByArgumentIndex setFormatByArgumentIndex} | ||
| 683 | * method, which accesses format elements based on the argument | ||
| 684 | * index they specify. | ||
| 685 | * | ||
| 686 | * @param formatElementIndex the index of a format element within the pattern | ||
| 687 | * @param newFormat the format to use for the specified format element | ||
| 688 | * @exception ArrayIndexOutOfBoundsException if {@code formatElementIndex} is equal to or | ||
| 689 | * larger than the number of format elements in the pattern string | ||
| 690 | */ | ||
| 691 | public void setFormat(int formatElementIndex, Format newFormat) { | ||
| 692 | formats[formatElementIndex] = newFormat; | ||
| 693 | } | ||
| 694 | |||
| 695 | /** | ||
| 696 | * Gets the formats used for the values passed into | ||
| 697 | * <code>format</code> methods or returned from <code>parse</code> | ||
| 698 | * methods. The indices of elements in the returned array | ||
| 699 | * correspond to the argument indices used in the previously set | ||
| 700 | * pattern string. | ||
| 701 | * The order of formats in the returned array thus corresponds to | ||
| 702 | * the order of elements in the <code>arguments</code> array passed | ||
| 703 | * to the <code>format</code> methods or the result array returned | ||
| 704 | * by the <code>parse</code> methods. | ||
| 705 | * <p> | ||
| 706 | * If an argument index is used for more than one format element | ||
| 707 | * in the pattern string, then the format used for the last such | ||
| 708 | * format element is returned in the array. If an argument index | ||
| 709 | * is not used for any format element in the pattern string, then | ||
| 710 | * null is returned in the array. | ||
| 711 | * | ||
| 712 | * @return the formats used for the arguments within the pattern | ||
| 713 | * @since 1.4 | ||
| 714 | */ | ||
| 715 | public Format[] getFormatsByArgumentIndex() { | ||
| 716 | int maximumArgumentNumber = -1; | ||
| 717 | for (int i = 0; i <= maxOffset; i++) { | ||
| 718 | if (argumentNumbers[i] > maximumArgumentNumber) { | ||
| 719 | maximumArgumentNumber = argumentNumbers[i]; | ||
| 720 | } | ||
| 721 | } | ||
| 722 | Format[] resultArray = new Format[maximumArgumentNumber + 1]; | ||
| 723 | for (int i = 0; i <= maxOffset; i++) { | ||
| 724 | resultArray[argumentNumbers[i]] = formats[i]; | ||
| 725 | } | ||
| 726 | return resultArray; | ||
| 727 | } | ||
| 728 | |||
| 729 | /** | ||
| 730 | * Gets the formats used for the format elements in the | ||
| 731 | * previously set pattern string. | ||
| 732 | * The order of formats in the returned array corresponds to | ||
| 733 | * the order of format elements in the pattern string. | ||
| 734 | * <p> | ||
| 735 | * Since the order of format elements in a pattern string often | ||
| 736 | * changes during localization, it's generally better to use the | ||
| 737 | * {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex} | ||
| 738 | * method, which assumes an order of formats corresponding to the | ||
| 739 | * order of elements in the <code>arguments</code> array passed to | ||
| 740 | * the <code>format</code> methods or the result array returned by | ||
| 741 | * the <code>parse</code> methods. | ||
| 742 | * | ||
| 743 | * @return the formats used for the format elements in the pattern | ||
| 744 | */ | ||
| 745 | public Format[] getFormats() { | ||
| 746 | Format[] resultArray = new Format[maxOffset + 1]; | ||
| 747 | System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1); | ||
| 748 | return resultArray; | ||
| 749 | } | ||
| 750 | |||
| 751 | /** | ||
| 752 | * Formats an array of objects and appends the <code>MessageFormat</code>'s | ||
| 753 | * pattern, with format elements replaced by the formatted objects, to the | ||
| 754 | * provided <code>StringBuffer</code>. | ||
| 755 | * <p> | ||
| 756 | * The text substituted for the individual format elements is derived from | ||
| 757 | * the current subformat of the format element and the | ||
| 758 | * <code>arguments</code> element at the format element's argument index | ||
| 759 | * as indicated by the first matching line of the following table. An | ||
| 760 | * argument is <i>unavailable</i> if <code>arguments</code> is | ||
| 761 | * <code>null</code> or has fewer than argumentIndex+1 elements. | ||
| 762 | * <p> | ||
| 763 | * <table border=1 summary="Examples of subformat,argument,and formatted text"> | ||
| 764 | * <tr> | ||
| 765 | * <th>Subformat | ||
| 766 | * <th>Argument | ||
| 767 | * <th>Formatted Text | ||
| 768 | * <tr> | ||
| 769 | * <td><i>any</i> | ||
| 770 | * <td><i>unavailable</i> | ||
| 771 | * <td><code>"{" + argumentIndex + "}"</code> | ||
| 772 | * <tr> | ||
| 773 | * <td><i>any</i> | ||
| 774 | * <td><code>null</code> | ||
| 775 | * <td><code>"null"</code> | ||
| 776 | * <tr> | ||
| 777 | * <td><code>instanceof ChoiceFormat</code> | ||
| 778 | * <td><i>any</i> | ||
| 779 | * <td><code>subformat.format(argument).indexOf('{') >= 0 ?<br> | ||
| 780 | * (new MessageFormat(subformat.format(argument), getLocale())).format(argument) : | ||
| 781 | * subformat.format(argument)</code> | ||
| 782 | * <tr> | ||
| 783 | * <td><code>!= null</code> | ||
| 784 | * <td><i>any</i> | ||
| 785 | * <td><code>subformat.format(argument)</code> | ||
| 786 | * <tr> | ||
| 787 | * <td><code>null</code> | ||
| 788 | * <td><code>instanceof Number</code> | ||
| 789 | * <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code> | ||
| 790 | * <tr> | ||
| 791 | * <td><code>null</code> | ||
| 792 | * <td><code>instanceof Date</code> | ||
| 793 | * <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)</code> | ||
| 794 | * <tr> | ||
| 795 | * <td><code>null</code> | ||
| 796 | * <td><code>instanceof String</code> | ||
| 797 | * <td><code>argument</code> | ||
| 798 | * <tr> | ||
| 799 | * <td><code>null</code> | ||
| 800 | * <td><i>any</i> | ||
| 801 | * <td><code>argument.toString()</code> | ||
| 802 | * </table> | ||
| 803 | * <p> | ||
| 804 | * If <code>pos</code> is non-null, and refers to | ||
| 805 | * <code>Field.ARGUMENT</code>, the location of the first formatted | ||
| 806 | * string will be returned. | ||
| 807 | * | ||
| 808 | * @param arguments an array of objects to be formatted and substituted. | ||
| 809 | * @param result where text is appended. | ||
| 810 | * @param pos On input: an alignment field, if desired. | ||
| 811 | * On output: the offsets of the alignment field. | ||
| 812 | * @exception IllegalArgumentException if an argument in the | ||
| 813 | * <code>arguments</code> array is not of the type | ||
| 814 | * expected by the format element(s) that use it. | ||
| 815 | */ | ||
| 816 | public final StringBuffer format(Object[] arguments, StringBuffer result, | ||
| 817 | FieldPosition pos) | ||
| 818 | { | ||
| 819 | return subformat(arguments, result, pos, null); | ||
| 820 | } | ||
| 821 | |||
| 822 | /** | ||
| 823 | * Creates a MessageFormat with the given pattern and uses it | ||
| 824 | * to format the given arguments. This is equivalent to | ||
| 825 | * <blockquote> | ||
| 826 | * <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code> | ||
| 827 | * </blockquote> | ||
| 828 | * | ||
| 829 | * @exception IllegalArgumentException if the pattern is invalid, | ||
| 830 | * or if an argument in the <code>arguments</code> array | ||
| 831 | * is not of the type expected by the format element(s) | ||
| 832 | * that use it. | ||
| 833 | */ | ||
| 834 | public static String format(String pattern, Object ... arguments) { | ||
| 835 | MessageFormat temp = new MessageFormat(pattern); | ||
| 836 | return temp.format(arguments); | ||
| 837 | } | ||
| 838 | |||
| 839 | // Overrides | ||
| 840 | /** | ||
| 841 | * Formats an array of objects and appends the <code>MessageFormat</code>'s | ||
| 842 | * pattern, with format elements replaced by the formatted objects, to the | ||
| 843 | * provided <code>StringBuffer</code>. | ||
| 844 | * This is equivalent to | ||
| 845 | * <blockquote> | ||
| 846 | * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code> | ||
| 847 | * </blockquote> | ||
| 848 | * | ||
| 849 | * @param arguments an array of objects to be formatted and substituted. | ||
| 850 | * @param result where text is appended. | ||
| 851 | * @param pos On input: an alignment field, if desired. | ||
| 852 | * On output: the offsets of the alignment field. | ||
| 853 | * @exception IllegalArgumentException if an argument in the | ||
| 854 | * <code>arguments</code> array is not of the type | ||
| 855 | * expected by the format element(s) that use it. | ||
| 856 | */ | ||
| 857 | public final StringBuffer format(Object arguments, StringBuffer result, | ||
| 858 | FieldPosition pos) | ||
| 859 | { | ||
| 860 | return subformat((Object[]) arguments, result, pos, null); | ||
| 861 | } | ||
| 862 | |||
| 863 | /** | ||
| 864 | * Formats an array of objects and inserts them into the | ||
| 865 | * <code>MessageFormat</code>'s pattern, producing an | ||
| 866 | * <code>AttributedCharacterIterator</code>. | ||
| 867 | * You can use the returned <code>AttributedCharacterIterator</code> | ||
| 868 | * to build the resulting String, as well as to determine information | ||
| 869 | * about the resulting String. | ||
| 870 | * <p> | ||
| 871 | * The text of the returned <code>AttributedCharacterIterator</code> is | ||
| 872 | * the same that would be returned by | ||
| 873 | * <blockquote> | ||
| 874 | * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code> | ||
| 875 | * </blockquote> | ||
| 876 | * <p> | ||
| 877 | * In addition, the <code>AttributedCharacterIterator</code> contains at | ||
| 878 | * least attributes indicating where text was generated from an | ||
| 879 | * argument in the <code>arguments</code> array. The keys of these attributes are of | ||
| 880 | * type <code>MessageFormat.Field</code>, their values are | ||
| 881 | * <code>Integer</code> objects indicating the index in the <code>arguments</code> | ||
| 882 | * array of the argument from which the text was generated. | ||
| 883 | * <p> | ||
| 884 | * The attributes/value from the underlying <code>Format</code> | ||
| 885 | * instances that <code>MessageFormat</code> uses will also be | ||
| 886 | * placed in the resulting <code>AttributedCharacterIterator</code>. | ||
| 887 | * This allows you to not only find where an argument is placed in the | ||
| 888 | * resulting String, but also which fields it contains in turn. | ||
| 889 | * | ||
| 890 | * @param arguments an array of objects to be formatted and substituted. | ||
| 891 | * @return AttributedCharacterIterator describing the formatted value. | ||
| 892 | * @exception NullPointerException if <code>arguments</code> is null. | ||
| 893 | * @exception IllegalArgumentException if an argument in the | ||
| 894 | * <code>arguments</code> array is not of the type | ||
| 895 | * expected by the format element(s) that use it. | ||
| 896 | * @since 1.4 | ||
| 897 | */ | ||
| 898 | public AttributedCharacterIterator formatToCharacterIterator(Object arguments) { | ||
| 899 | StringBuffer result = new StringBuffer(); | ||
| 900 | ArrayList iterators = new ArrayList(); | ||
| 901 | |||
| 902 | if (arguments == null) { | ||
| 903 | throw new NullPointerException( | ||
| 904 | "formatToCharacterIterator must be passed non-null object"); | ||
| 905 | } | ||
| 906 | subformat((Object[]) arguments, result, null, iterators); | ||
| 907 | if (iterators.size() == 0) { | ||
| 908 | return createAttributedCharacterIterator(""); | ||
| 909 | } | ||
| 910 | return createAttributedCharacterIterator( | ||
| 911 | (AttributedCharacterIterator[])iterators.toArray( | ||
| 912 | new AttributedCharacterIterator[iterators.size()])); | ||
| 913 | } | ||
| 914 | |||
| 915 | /** | ||
| 916 | * Parses the string. | ||
| 917 | * | ||
| 918 | * <p>Caveats: The parse may fail in a number of circumstances. | ||
| 919 | * For example: | ||
| 920 | * <ul> | ||
| 921 | * <li>If one of the arguments does not occur in the pattern. | ||
| 922 | * <li>If the format of an argument loses information, such as | ||
| 923 | * with a choice format where a large number formats to "many". | ||
| 924 | * <li>Does not yet handle recursion (where | ||
| 925 | * the substituted strings contain {n} references.) | ||
| 926 | * <li>Will not always find a match (or the correct match) | ||
| 927 | * if some part of the parse is ambiguous. | ||
| 928 | * For example, if the pattern "{1},{2}" is used with the | ||
| 929 | * string arguments {"a,b", "c"}, it will format as "a,b,c". | ||
| 930 | * When the result is parsed, it will return {"a", "b,c"}. | ||
| 931 | * <li>If a single argument is parsed more than once in the string, | ||
| 932 | * then the later parse wins. | ||
| 933 | * </ul> | ||
| 934 | * When the parse fails, use ParsePosition.getErrorIndex() to find out | ||
| 935 | * where in the string the parsing failed. The returned error | ||
| 936 | * index is the starting offset of the sub-patterns that the string | ||
| 937 | * is comparing with. For example, if the parsing string "AAA {0} BBB" | ||
| 938 | * is comparing against the pattern "AAD {0} BBB", the error index is | ||
| 939 | * 0. When an error occurs, the call to this method will return null. | ||
| 940 | * If the source is null, return an empty array. | ||
| 941 | */ | ||
| 942 | public Object[] parse(String source, ParsePosition pos) { | ||
| 943 | if (source == null) { | ||
| 944 | Object[] empty = {}; | ||
| 945 | return empty; | ||
| 946 | } | ||
| 947 | |||
| 948 | int maximumArgumentNumber = -1; | ||
| 949 | for (int i = 0; i <= maxOffset; i++) { | ||
| 950 | if (argumentNumbers[i] > maximumArgumentNumber) { | ||
| 951 | maximumArgumentNumber = argumentNumbers[i]; | ||
| 952 | } | ||
| 953 | } | ||
| 954 | Object[] resultArray = new Object[maximumArgumentNumber + 1]; | ||
| 955 | |||
| 956 | int patternOffset = 0; | ||
| 957 | int sourceOffset = pos.index; | ||
| 958 | ParsePosition tempStatus = new ParsePosition(0); | ||
| 959 | for (int i = 0; i <= maxOffset; ++i) { | ||
| 960 | // match up to format | ||
| 961 | int len = offsets[i] - patternOffset; | ||
| 962 | if (len == 0 || pattern.regionMatches(patternOffset, | ||
| 963 | source, sourceOffset, len)) { | ||
| 964 | sourceOffset += len; | ||
| 965 | patternOffset += len; | ||
| 966 | } else { | ||
| 967 | pos.errorIndex = sourceOffset; | ||
| 968 | return null; // leave index as is to signal error | ||
| 969 | } | ||
| 970 | |||
| 971 | // now use format | ||
| 972 | if (formats[i] == null) { // string format | ||
| 973 | // if at end, use longest possible match | ||
| 974 | // otherwise uses first match to intervening string | ||
| 975 | // does NOT recursively try all possibilities | ||
| 976 | int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length(); | ||
| 977 | |||
| 978 | int next; | ||
| 979 | if (patternOffset >= tempLength) { | ||
| 980 | next = source.length(); | ||
| 981 | }else{ | ||
| 982 | next = source.indexOf(pattern.substring(patternOffset, tempLength), | ||
| 983 | sourceOffset); | ||
| 984 | } | ||
| 985 | |||
| 986 | if (next < 0) { | ||
| 987 | pos.errorIndex = sourceOffset; | ||
| 988 | return null; // leave index as is to signal error | ||
| 989 | } else { | ||
| 990 | String strValue= source.substring(sourceOffset,next); | ||
| 991 | if (!strValue.equals("{"+argumentNumbers[i]+"}")) | ||
| 992 | resultArray[argumentNumbers[i]] | ||
| 993 | = source.substring(sourceOffset,next); | ||
| 994 | sourceOffset = next; | ||
| 995 | } | ||
| 996 | } else { | ||
| 997 | tempStatus.index = sourceOffset; | ||
| 998 | resultArray[argumentNumbers[i]] | ||
| 999 | = formats[i].parseObject(source,tempStatus); | ||
| 1000 | if (tempStatus.index == sourceOffset) { | ||
| 1001 | pos.errorIndex = sourceOffset; | ||
| 1002 | return null; // leave index as is to signal error | ||
| 1003 | } | ||
| 1004 | sourceOffset = tempStatus.index; // update | ||
| 1005 | } | ||
| 1006 | } | ||
| 1007 | int len = pattern.length() - patternOffset; | ||
| 1008 | if (len == 0 || pattern.regionMatches(patternOffset, | ||
| 1009 | source, sourceOffset, len)) { | ||
| 1010 | pos.index = sourceOffset + len; | ||
| 1011 | } else { | ||
| 1012 | pos.errorIndex = sourceOffset; | ||
| 1013 | return null; // leave index as is to signal error | ||
| 1014 | } | ||
| 1015 | return resultArray; | ||
| 1016 | } | ||
| 1017 | |||
| 1018 | /** | ||
| 1019 | * Parses text from the beginning of the given string to produce an object | ||
| 1020 | * array. | ||
| 1021 | * The method may not use the entire text of the given string. | ||
| 1022 | * <p> | ||
| 1023 | * See the {@link #parse(String, ParsePosition)} method for more information | ||
| 1024 | * on message parsing. | ||
| 1025 | * | ||
| 1026 | * @param source A <code>String</code> whose beginning should be parsed. | ||
| 1027 | * @return An <code>Object</code> array parsed from the string. | ||
| 1028 | * @exception ParseException if the beginning of the specified string | ||
| 1029 | * cannot be parsed. | ||
| 1030 | */ | ||
| 1031 | public Object[] parse(String source) throws ParseException { | ||
| 1032 | ParsePosition pos = new ParsePosition(0); | ||
| 1033 | Object[] result = parse(source, pos); | ||
| 1034 | if (pos.index == 0) // unchanged, returned object is null | ||
| 1035 | throw new ParseException("MessageFormat parse error!", pos.errorIndex); | ||
| 1036 | |||
| 1037 | return result; | ||
| 1038 | } | ||
| 1039 | |||
| 1040 | /** | ||
| 1041 | * Parses text from a string to produce an object array. | ||
| 1042 | * <p> | ||
| 1043 | * The method attempts to parse text starting at the index given by | ||
| 1044 | * <code>pos</code>. | ||
| 1045 | * If parsing succeeds, then the index of <code>pos</code> is updated | ||
| 1046 | * to the index after the last character used (parsing does not necessarily | ||
| 1047 | * use all characters up to the end of the string), and the parsed | ||
| 1048 | * object array is returned. The updated <code>pos</code> can be used to | ||
| 1049 | * indicate the starting point for the next call to this method. | ||
| 1050 | * If an error occurs, then the index of <code>pos</code> is not | ||
| 1051 | * changed, the error index of <code>pos</code> is set to the index of | ||
| 1052 | * the character where the error occurred, and null is returned. | ||
| 1053 | * <p> | ||
| 1054 | * See the {@link #parse(String, ParsePosition)} method for more information | ||
| 1055 | * on message parsing. | ||
| 1056 | * | ||
| 1057 | * @param source A <code>String</code>, part of which should be parsed. | ||
| 1058 | * @param pos A <code>ParsePosition</code> object with index and error | ||
| 1059 | * index information as described above. | ||
| 1060 | * @return An <code>Object</code> array parsed from the string. In case of | ||
| 1061 | * error, returns null. | ||
| 1062 | * @exception NullPointerException if <code>pos</code> is null. | ||
| 1063 | */ | ||
| 1064 | public Object parseObject(String source, ParsePosition pos) { | ||
| 1065 | return parse(source, pos); | ||
| 1066 | } | ||
| 1067 | |||
| 1068 | /** | ||
| 1069 | * Creates and returns a copy of this object. | ||
| 1070 | * | ||
| 1071 | * @return a clone of this instance. | ||
| 1072 | */ | ||
| 1073 | public Object clone() { | ||
| 1074 | MessageFormat other = (MessageFormat) super.clone(); | ||
| 1075 | |||
| 1076 | // clone arrays. Can't do with utility because of bug in Cloneable | ||
| 1077 | other.formats = (Format[]) formats.clone(); // shallow clone | ||
| 1078 | for (int i = 0; i < formats.length; ++i) { | ||
| 1079 | if (formats[i] != null) | ||
| 1080 | other.formats[i] = (Format)formats[i].clone(); | ||
| 1081 | } | ||
| 1082 | // for primitives or immutables, shallow clone is enough | ||
| 1083 | other.offsets = (int[]) offsets.clone(); | ||
| 1084 | other.argumentNumbers = (int[]) argumentNumbers.clone(); | ||
| 1085 | |||
| 1086 | return other; | ||
| 1087 | } | ||
| 1088 | |||
| 1089 | /** | ||
| 1090 | * Equality comparison between two message format objects | ||
| 1091 | */ | ||
| 1092 | public boolean equals(Object obj) { | ||
| 1093 | if (this == obj) // quick check | ||
| 1094 | return true; | ||
| 1095 | if (obj == null || getClass() != obj.getClass()) | ||
| 1096 | return false; | ||
| 1097 | MessageFormat other = (MessageFormat) obj; | ||
| 1098 | return (maxOffset == other.maxOffset | ||
| 1099 | && pattern.equals(other.pattern) | ||
| 1100 | && ((locale != null && locale.equals(other.locale)) | ||
| 1101 | || (locale == null && other.locale == null)) | ||
| 1102 | && Arrays.equals(offsets,other.offsets) | ||
| 1103 | && Arrays.equals(argumentNumbers,other.argumentNumbers) | ||
| 1104 | && Arrays.equals(formats,other.formats)); | ||
| 1105 | } | ||
| 1106 | |||
| 1107 | /** | ||
| 1108 | * Generates a hash code for the message format object. | ||
| 1109 | */ | ||
| 1110 | public int hashCode() { | ||
| 1111 | return pattern.hashCode(); // enough for reasonable distribution | ||
| 1112 | } | ||
| 1113 | |||
| 1114 | |||
| 1115 | /** | ||
| 1116 | * Defines constants that are used as attribute keys in the | ||
| 1117 | * <code>AttributedCharacterIterator</code> returned | ||
| 1118 | * from <code>MessageFormat.formatToCharacterIterator</code>. | ||
| 1119 | * | ||
| 1120 | * @since 1.4 | ||
| 1121 | */ | ||
| 1122 | public static class Field extends Format.Field { | ||
| 1123 | |||
| 1124 | // Proclaim serial compatibility with 1.4 FCS | ||
| 1125 | private static final long serialVersionUID = 7899943957617360810L; | ||
| 1126 | |||
| 1127 | /** | ||
| 1128 | * Creates a Field with the specified name. | ||
| 1129 | * | ||
| 1130 | * @param name Name of the attribute | ||
| 1131 | */ | ||
| 1132 | protected Field(String name) { | ||
| 1133 | super(name); | ||
| 1134 | } | ||
| 1135 | |||
| 1136 | /** | ||
| 1137 | * Resolves instances being deserialized to the predefined constants. | ||
| 1138 | * | ||
| 1139 | * @throws InvalidObjectException if the constant could not be | ||
| 1140 | * resolved. | ||
| 1141 | * @return resolved MessageFormat.Field constant | ||
| 1142 | */ | ||
| 1143 | protected Object readResolve() throws InvalidObjectException { | ||
| 1144 | if (this.getClass() != MessageFormat.Field.class) { | ||
| 1145 | throw new InvalidObjectException("subclass didn't correctly implement readResolve"); | ||
| 1146 | } | ||
| 1147 | |||
| 1148 | return ARGUMENT; | ||
| 1149 | } | ||
| 1150 | |||
| 1151 | // | ||
| 1152 | // The constants | ||
| 1153 | // | ||
| 1154 | |||
| 1155 | /** | ||
| 1156 | * Constant identifying a portion of a message that was generated | ||
| 1157 | * from an argument passed into <code>formatToCharacterIterator</code>. | ||
| 1158 | * The value associated with the key will be an <code>Integer</code> | ||
| 1159 | * indicating the index in the <code>arguments</code> array of the | ||
| 1160 | * argument from which the text was generated. | ||
| 1161 | */ | ||
| 1162 | public final static Field ARGUMENT = | ||
| 1163 | new Field("message argument field"); | ||
| 1164 | } | ||
| 1165 | |||
| 1166 | // ===========================privates============================ | ||
| 1167 | |||
| 1168 | /** | ||
| 1169 | * The locale to use for formatting numbers and dates. | ||
| 1170 | * @serial | ||
| 1171 | */ | ||
| 1172 | private Locale locale; | ||
| 1173 | |||
| 1174 | /** | ||
| 1175 | * The string that the formatted values are to be plugged into. In other words, this | ||
| 1176 | * is the pattern supplied on construction with all of the {} expressions taken out. | ||
| 1177 | * @serial | ||
| 1178 | */ | ||
| 1179 | private String pattern = ""; | ||
| 1180 | |||
| 1181 | /** The initially expected number of subformats in the format */ | ||
| 1182 | private static final int INITIAL_FORMATS = 10; | ||
| 1183 | |||
| 1184 | /** | ||
| 1185 | * An array of formatters, which are used to format the arguments. | ||
| 1186 | * @serial | ||
| 1187 | */ | ||
| 1188 | private Format[] formats = new Format[INITIAL_FORMATS]; | ||
| 1189 | |||
| 1190 | /** | ||
| 1191 | * The positions where the results of formatting each argument are to be inserted | ||
| 1192 | * into the pattern. | ||
| 1193 | * @serial | ||
| 1194 | */ | ||
| 1195 | private int[] offsets = new int[INITIAL_FORMATS]; | ||
| 1196 | |||
| 1197 | /** | ||
| 1198 | * The argument numbers corresponding to each formatter. (The formatters are stored | ||
| 1199 | * in the order they occur in the pattern, not in the order in which the arguments | ||
| 1200 | * are specified.) | ||
| 1201 | * @serial | ||
| 1202 | */ | ||
| 1203 | private int[] argumentNumbers = new int[INITIAL_FORMATS]; | ||
| 1204 | |||
| 1205 | /** | ||
| 1206 | * One less than the number of entries in <code>offsets</code>. Can also be thought of | ||
| 1207 | * as the index of the highest-numbered element in <code>offsets</code> that is being used. | ||
| 1208 | * All of these arrays should have the same number of elements being used as <code>offsets</code> | ||
| 1209 | * does, and so this variable suffices to tell us how many entries are in all of them. | ||
| 1210 | * @serial | ||
| 1211 | */ | ||
| 1212 | private int maxOffset = -1; | ||
| 1213 | |||
| 1214 | /** | ||
| 1215 | * Internal routine used by format. If <code>characterIterators</code> is | ||
| 1216 | * non-null, AttributedCharacterIterator will be created from the | ||
| 1217 | * subformats as necessary. If <code>characterIterators</code> is null | ||
| 1218 | * and <code>fp</code> is non-null and identifies | ||
| 1219 | * <code>Field.MESSAGE_ARGUMENT</code>, the location of | ||
| 1220 | * the first replaced argument will be set in it. | ||
| 1221 | * | ||
| 1222 | * @exception IllegalArgumentException if an argument in the | ||
| 1223 | * <code>arguments</code> array is not of the type | ||
| 1224 | * expected by the format element(s) that use it. | ||
| 1225 | */ | ||
| 1226 | private StringBuffer subformat(Object[] arguments, StringBuffer result, | ||
| 1227 | FieldPosition fp, List characterIterators) { | ||
| 1228 | // note: this implementation assumes a fast substring & index. | ||
| 1229 | // if this is not true, would be better to append chars one by one. | ||
| 1230 | int lastOffset = 0; | ||
| 1231 | int last = result.length(); | ||
| 1232 | for (int i = 0; i <= maxOffset; ++i) { | ||
| 1233 | result.append(pattern.substring(lastOffset, offsets[i])); | ||
| 1234 | lastOffset = offsets[i]; | ||
| 1235 | int argumentNumber = argumentNumbers[i]; | ||
| 1236 | if (arguments == null || argumentNumber >= arguments.length) { | ||
| 1237 | result.append('{').append(argumentNumber).append('}'); | ||
| 1238 | continue; | ||
| 1239 | } | ||
| 1240 | // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3); | ||
| 1241 | if (false) { // if (argRecursion == 3){ | ||
| 1242 | // prevent loop!!! | ||
| 1243 | result.append('\uFFFD'); | ||
| 1244 | } else { | ||
| 1245 | Object obj = arguments[argumentNumber]; | ||
| 1246 | String arg = null; | ||
| 1247 | Format subFormatter = null; | ||
| 1248 | if (obj == null) { | ||
| 1249 | arg = "null"; | ||
| 1250 | } else if (formats[i] != null) { | ||
| 1251 | subFormatter = formats[i]; | ||
| 1252 | if (subFormatter instanceof ChoiceFormat) { | ||
| 1253 | arg = formats[i].format(obj); | ||
| 1254 | if (arg.indexOf('{') >= 0) { | ||
| 1255 | subFormatter = new MessageFormat(arg, locale); | ||
| 1256 | obj = arguments; | ||
| 1257 | arg = null; | ||
| 1258 | } | ||
| 1259 | } | ||
| 1260 | } else if (obj instanceof Number) { | ||
| 1261 | // format number if can | ||
| 1262 | subFormatter = NumberFormat.getInstance(locale); | ||
| 1263 | } else if (obj instanceof Date) { | ||
| 1264 | // format a Date if can | ||
| 1265 | subFormatter = DateFormat.getDateTimeInstance( | ||
| 1266 | DateFormat.SHORT, DateFormat.SHORT, locale);//fix | ||
| 1267 | } else if (obj instanceof String) { | ||
| 1268 | arg = (String) obj; | ||
| 1269 | |||
| 1270 | } else { | ||
| 1271 | arg = obj.toString(); | ||
| 1272 | if (arg == null) arg = "null"; | ||
| 1273 | } | ||
| 1274 | |||
| 1275 | // At this point we are in two states, either subFormatter | ||
| 1276 | // is non-null indicating we should format obj using it, | ||
| 1277 | // or arg is non-null and we should use it as the value. | ||
| 1278 | |||
| 1279 | if (characterIterators != null) { | ||
| 1280 | // If characterIterators is non-null, it indicates we need | ||
| 1281 | // to get the CharacterIterator from the child formatter. | ||
| 1282 | if (last != result.length()) { | ||
| 1283 | characterIterators.add( | ||
| 1284 | createAttributedCharacterIterator(result.substring | ||
| 1285 | (last))); | ||
| 1286 | last = result.length(); | ||
| 1287 | } | ||
| 1288 | if (subFormatter != null) { | ||
| 1289 | AttributedCharacterIterator subIterator = | ||
| 1290 | subFormatter.formatToCharacterIterator(obj); | ||
| 1291 | |||
| 1292 | append(result, subIterator); | ||
| 1293 | if (last != result.length()) { | ||
| 1294 | characterIterators.add( | ||
| 1295 | createAttributedCharacterIterator( | ||
| 1296 | subIterator, Field.ARGUMENT, | ||
| 1297 | Integer.valueOf(argumentNumber))); | ||
| 1298 | last = result.length(); | ||
| 1299 | } | ||
| 1300 | arg = null; | ||
| 1301 | } | ||
| 1302 | if (arg != null && arg.length() > 0) { | ||
| 1303 | result.append(arg); | ||
| 1304 | characterIterators.add( | ||
| 1305 | createAttributedCharacterIterator( | ||
| 1306 | arg, Field.ARGUMENT, | ||
| 1307 | Integer.valueOf(argumentNumber))); | ||
| 1308 | last = result.length(); | ||
| 1309 | } | ||
| 1310 | } | ||
| 1311 | else { | ||
| 1312 | if (subFormatter != null) { | ||
| 1313 | arg = subFormatter.format(obj); | ||
| 1314 | } | ||
| 1315 | last = result.length(); | ||
| 1316 | result.append(arg); | ||
| 1317 | if (i == 0 && fp != null && Field.ARGUMENT.equals( | ||
| 1318 | fp.getFieldAttribute())) { | ||
| 1319 | fp.setBeginIndex(last); | ||
| 1320 | fp.setEndIndex(result.length()); | ||
| 1321 | } | ||
| 1322 | last = result.length(); | ||
| 1323 | } | ||
| 1324 | } | ||
| 1325 | } | ||
| 1326 | result.append(pattern.substring(lastOffset, pattern.length())); | ||
| 1327 | if (characterIterators != null && last != result.length()) { | ||
| 1328 | characterIterators.add(createAttributedCharacterIterator( | ||
| 1329 | result.substring(last))); | ||
| 1330 | } | ||
| 1331 | return result; | ||
| 1332 | } | ||
| 1333 | |||
| 1334 | /** | ||
| 1335 | * Convenience method to append all the characters in | ||
| 1336 | * <code>iterator</code> to the StringBuffer <code>result</code>. | ||
| 1337 | */ | ||
| 1338 | private void append(StringBuffer result, CharacterIterator iterator) { | ||
| 1339 | if (iterator.first() != CharacterIterator.DONE) { | ||
| 1340 | char aChar; | ||
| 1341 | |||
| 1342 | result.append(iterator.first()); | ||
| 1343 | while ((aChar = iterator.next()) != CharacterIterator.DONE) { | ||
| 1344 | result.append(aChar); | ||
| 1345 | } | ||
| 1346 | } | ||
| 1347 | } | ||
| 1348 | |||
| 1349 | // Indices for segments | ||
| 1350 | private static final int SEG_RAW = 0; | ||
| 1351 | private static final int SEG_INDEX = 1; | ||
| 1352 | private static final int SEG_TYPE = 2; | ||
| 1353 | private static final int SEG_MODIFIER = 3; // modifier or subformat | ||
| 1354 | |||
| 1355 | // Indices for type keywords | ||
| 1356 | private static final int TYPE_NULL = 0; | ||
| 1357 | private static final int TYPE_NUMBER = 1; | ||
| 1358 | private static final int TYPE_DATE = 2; | ||
| 1359 | private static final int TYPE_TIME = 3; | ||
| 1360 | private static final int TYPE_CHOICE = 4; | ||
| 1361 | |||
| 1362 | private static final String[] TYPE_KEYWORDS = { | ||
| 1363 | "", | ||
| 1364 | "number", | ||
| 1365 | "date", | ||
| 1366 | "time", | ||
| 1367 | "choice" | ||
| 1368 | }; | ||
| 1369 | |||
| 1370 | // Indices for number modifiers | ||
| 1371 | private static final int MODIFIER_DEFAULT = 0; // common in number and date-time | ||
| 1372 | private static final int MODIFIER_CURRENCY = 1; | ||
| 1373 | private static final int MODIFIER_PERCENT = 2; | ||
| 1374 | private static final int MODIFIER_INTEGER = 3; | ||
| 1375 | |||
| 1376 | private static final String[] NUMBER_MODIFIER_KEYWORDS = { | ||
| 1377 | "", | ||
| 1378 | "currency", | ||
| 1379 | "percent", | ||
| 1380 | "integer" | ||
| 1381 | }; | ||
| 1382 | |||
| 1383 | // Indices for date-time modifiers | ||
| 1384 | private static final int MODIFIER_SHORT = 1; | ||
| 1385 | private static final int MODIFIER_MEDIUM = 2; | ||
| 1386 | private static final int MODIFIER_LONG = 3; | ||
| 1387 | private static final int MODIFIER_FULL = 4; | ||
| 1388 | |||
| 1389 | private static final String[] DATE_TIME_MODIFIER_KEYWORDS = { | ||
| 1390 | "", | ||
| 1391 | "short", | ||
| 1392 | "medium", | ||
| 1393 | "long", | ||
| 1394 | "full" | ||
| 1395 | }; | ||
| 1396 | |||
| 1397 | // Date-time style values corresponding to the date-time modifiers. | ||
| 1398 | private static final int[] DATE_TIME_MODIFIERS = { | ||
| 1399 | DateFormat.DEFAULT, | ||
| 1400 | DateFormat.SHORT, | ||
| 1401 | DateFormat.MEDIUM, | ||
| 1402 | DateFormat.LONG, | ||
| 1403 | DateFormat.FULL, | ||
| 1404 | }; | ||
| 1405 | |||
| 1406 | private void makeFormat(int position, int offsetNumber, | ||
| 1407 | StringBuilder[] textSegments) | ||
| 1408 | { | ||
| 1409 | String[] segments = new String[textSegments.length]; | ||
| 1410 | for (int i = 0; i < textSegments.length; i++) { | ||
| 1411 | StringBuilder oneseg = textSegments[i]; | ||
| 1412 | segments[i] = (oneseg != null) ? oneseg.toString() : ""; | ||
| 1413 | } | ||
| 1414 | |||
| 1415 | // get the argument number | ||
| 1416 | int argumentNumber; | ||
| 1417 | try { | ||
| 1418 | argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always unlocalized! | ||
| 1419 | } catch (NumberFormatException e) { | ||
| 1420 | throw new IllegalArgumentException("can't parse argument number: " | ||
| 1421 | + segments[SEG_INDEX], e); | ||
| 1422 | } | ||
| 1423 | if (argumentNumber < 0) { | ||
| 1424 | throw new IllegalArgumentException("negative argument number: " | ||
| 1425 | + argumentNumber); | ||
| 1426 | } | ||
| 1427 | |||
| 1428 | // resize format information arrays if necessary | ||
| 1429 | if (offsetNumber >= formats.length) { | ||
| 1430 | int newLength = formats.length * 2; | ||
| 1431 | Format[] newFormats = new Format[newLength]; | ||
| 1432 | int[] newOffsets = new int[newLength]; | ||
| 1433 | int[] newArgumentNumbers = new int[newLength]; | ||
| 1434 | System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1); | ||
| 1435 | System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1); | ||
| 1436 | System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1); | ||
| 1437 | formats = newFormats; | ||
| 1438 | offsets = newOffsets; | ||
| 1439 | argumentNumbers = newArgumentNumbers; | ||
| 1440 | } | ||
| 1441 | int oldMaxOffset = maxOffset; | ||
| 1442 | maxOffset = offsetNumber; | ||
| 1443 | offsets[offsetNumber] = segments[SEG_RAW].length(); | ||
| 1444 | argumentNumbers[offsetNumber] = argumentNumber; | ||
| 1445 | |||
| 1446 | // now get the format | ||
| 1447 | Format newFormat = null; | ||
| 1448 | if (segments[SEG_TYPE].length() != 0) { | ||
| 1449 | int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS); | ||
| 1450 | switch (type) { | ||
| 1451 | case TYPE_NULL: | ||
| 1452 | // Type "" is allowed. e.g., "{0,}", "{0,,}", and "{0,,#}" | ||
| 1453 | // are treated as "{0}". | ||
| 1454 | break; | ||
| 1455 | |||
| 1456 | case TYPE_NUMBER: | ||
| 1457 | switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) { | ||
| 1458 | case MODIFIER_DEFAULT: | ||
| 1459 | newFormat = NumberFormat.getInstance(locale); | ||
| 1460 | break; | ||
| 1461 | case MODIFIER_CURRENCY: | ||
| 1462 | newFormat = NumberFormat.getCurrencyInstance(locale); | ||
| 1463 | break; | ||
| 1464 | case MODIFIER_PERCENT: | ||
| 1465 | newFormat = NumberFormat.getPercentInstance(locale); | ||
| 1466 | break; | ||
| 1467 | case MODIFIER_INTEGER: | ||
| 1468 | newFormat = NumberFormat.getIntegerInstance(locale); | ||
| 1469 | break; | ||
| 1470 | default: // DecimalFormat pattern | ||
| 1471 | try { | ||
| 1472 | newFormat = new DecimalFormat(segments[SEG_MODIFIER], | ||
| 1473 | DecimalFormatSymbols.getInstance(locale)); | ||
| 1474 | } catch (IllegalArgumentException e) { | ||
| 1475 | maxOffset = oldMaxOffset; | ||
| 1476 | throw e; | ||
| 1477 | } | ||
| 1478 | break; | ||
| 1479 | } | ||
| 1480 | break; | ||
| 1481 | |||
| 1482 | case TYPE_DATE: | ||
| 1483 | case TYPE_TIME: | ||
| 1484 | int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS); | ||
| 1485 | if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) { | ||
| 1486 | if (type == TYPE_DATE) { | ||
| 1487 | newFormat = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[mod], | ||
| 1488 | locale); | ||
| 1489 | } else { | ||
| 1490 | newFormat = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[mod], | ||
| 1491 | locale); | ||
| 1492 | } | ||
| 1493 | } else { | ||
| 1494 | // SimpleDateFormat pattern | ||
| 1495 | try { | ||
| 1496 | newFormat = new SimpleDateFormat(segments[SEG_MODIFIER], locale); | ||
| 1497 | } catch (IllegalArgumentException e) { | ||
| 1498 | maxOffset = oldMaxOffset; | ||
| 1499 | throw e; | ||
| 1500 | } | ||
| 1501 | } | ||
| 1502 | break; | ||
| 1503 | |||
| 1504 | case TYPE_CHOICE: | ||
| 1505 | try { | ||
| 1506 | // ChoiceFormat pattern | ||
| 1507 | newFormat = new ChoiceFormat(segments[SEG_MODIFIER]); | ||
| 1508 | } catch (Exception e) { | ||
| 1509 | maxOffset = oldMaxOffset; | ||
| 1510 | throw new IllegalArgumentException("Choice Pattern incorrect: " | ||
| 1511 | + segments[SEG_MODIFIER], e); | ||
| 1512 | } | ||
| 1513 | break; | ||
| 1514 | |||
| 1515 | default: | ||
| 1516 | maxOffset = oldMaxOffset; | ||
| 1517 | throw new IllegalArgumentException("unknown format type: " + | ||
| 1518 | segments[SEG_TYPE]); | ||
| 1519 | } | ||
| 1520 | } | ||
| 1521 | formats[offsetNumber] = newFormat; | ||
| 1522 | } | ||
| 1523 | |||
| 1524 | private static final int findKeyword(String s, String[] list) { | ||
| 1525 | for (int i = 0; i < list.length; ++i) { | ||
| 1526 | if (s.equals(list[i])) | ||
| 1527 | return i; | ||
| 1528 | } | ||
| 1529 | |||
| 1530 | // Try trimmed lowercase. | ||
| 1531 | String ls = s.trim().toLowerCase(Locale.ROOT); | ||
| 1532 | if (ls != s) { | ||
| 1533 | for (int i = 0; i < list.length; ++i) { | ||
| 1534 | if (ls.equals(list[i])) | ||
| 1535 | return i; | ||
| 1536 | } | ||
| 1537 | } | ||
| 1538 | return -1; | ||
| 1539 | } | ||
| 1540 | |||
| 1541 | private static final void copyAndFixQuotes(String source, int start, int end, | ||
| 1542 | StringBuilder target) { | ||
| 1543 | boolean quoted = false; | ||
| 1544 | |||
| 1545 | for (int i = start; i < end; ++i) { | ||
| 1546 | char ch = source.charAt(i); | ||
| 1547 | if (ch == '{') { | ||
| 1548 | if (!quoted) { | ||
| 1549 | target.append('\''); | ||
| 1550 | quoted = true; | ||
| 1551 | } | ||
| 1552 | target.append(ch); | ||
| 1553 | } else if (ch == '\'') { | ||
| 1554 | target.append("''"); | ||
| 1555 | } else { | ||
| 1556 | if (quoted) { | ||
| 1557 | target.append('\''); | ||
| 1558 | quoted = false; | ||
| 1559 | } | ||
| 1560 | target.append(ch); | ||
| 1561 | } | ||
| 1562 | } | ||
| 1563 | if (quoted) { | ||
| 1564 | target.append('\''); | ||
| 1565 | } | ||
| 1566 | } | ||
| 1567 | |||
| 1568 | /** | ||
| 1569 | * After reading an object from the input stream, do a simple verification | ||
| 1570 | * to maintain class invariants. | ||
| 1571 | * @throws InvalidObjectException if the objects read from the stream is invalid. | ||
| 1572 | */ | ||
| 1573 | private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { | ||
| 1574 | in.defaultReadObject(); | ||
| 1575 | boolean isValid = maxOffset >= -1 | ||
| 1576 | && formats.length > maxOffset | ||
| 1577 | && offsets.length > maxOffset | ||
| 1578 | && argumentNumbers.length > maxOffset; | ||
| 1579 | if (isValid) { | ||
| 1580 | int lastOffset = pattern.length() + 1; | ||
| 1581 | for (int i = maxOffset; i >= 0; --i) { | ||
| 1582 | if ((offsets[i] < 0) || (offsets[i] > lastOffset)) { | ||
| 1583 | isValid = false; | ||
| 1584 | break; | ||
| 1585 | } else { | ||
| 1586 | lastOffset = offsets[i]; | ||
| 1587 | } | ||
| 1588 | } | ||
| 1589 | } | ||
| 1590 | if (!isValid) { | ||
| 1591 | throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream."); | ||
| 1592 | } | ||
| 1593 | } | ||
| 1594 | } | ||
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,183 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | ******************************************************************************* | ||
| 28 | * (C) Copyright IBM Corp. 1996-2005 - All Rights Reserved * | ||
| 29 | * * | ||
| 30 | * The original version of this source code and documentation is copyrighted * | ||
| 31 | * and owned by IBM, These materials are provided under terms of a License * | ||
| 32 | * Agreement between IBM and Sun. This technology is protected by multiple * | ||
| 33 | * US and International patents. This notice and attribution to IBM may not * | ||
| 34 | * to removed. * | ||
| 35 | ******************************************************************************* | ||
| 36 | */ | ||
| 37 | |||
| 38 | package java.text; | ||
| 39 | |||
| 40 | import sun.text.normalizer.NormalizerBase; | ||
| 41 | import sun.text.normalizer.NormalizerImpl; | ||
| 42 | |||
| 43 | /** | ||
| 44 | * This class provides the method <code>normalize</code> which transforms Unicode | ||
| 45 | * text into an equivalent composed or decomposed form, allowing for easier | ||
| 46 | * sorting and searching of text. | ||
| 47 | * The <code>normalize</code> method supports the standard normalization forms | ||
| 48 | * described in | ||
| 49 | * <a href="http://www.unicode.org/unicode/reports/tr15/tr15-23.html"> | ||
| 50 | * Unicode Standard Annex #15 — Unicode Normalization Forms</a>. | ||
| 51 | * <p> | ||
| 52 | * Characters with accents or other adornments can be encoded in | ||
| 53 | * several different ways in Unicode. For example, take the character A-acute. | ||
| 54 | * In Unicode, this can be encoded as a single character (the "composed" form): | ||
| 55 | * | ||
| 56 | * <p><pre> | ||
| 57 | * U+00C1 LATIN CAPITAL LETTER A WITH ACUTE</pre> | ||
| 58 | * </p> | ||
| 59 | * | ||
| 60 | * or as two separate characters (the "decomposed" form): | ||
| 61 | * | ||
| 62 | * <p><pre> | ||
| 63 | * U+0041 LATIN CAPITAL LETTER A | ||
| 64 | * U+0301 COMBINING ACUTE ACCENT</pre> | ||
| 65 | * </p> | ||
| 66 | * | ||
| 67 | * To a user of your program, however, both of these sequences should be | ||
| 68 | * treated as the same "user-level" character "A with acute accent". When you | ||
| 69 | * are searching or comparing text, you must ensure that these two sequences are | ||
| 70 | * treated as equivalent. In addition, you must handle characters with more than | ||
| 71 | * one accent. Sometimes the order of a character's combining accents is | ||
| 72 | * significant, while in other cases accent sequences in different orders are | ||
| 73 | * really equivalent. | ||
| 74 | * <p> | ||
| 75 | * Similarly, the string "ffi" can be encoded as three separate letters: | ||
| 76 | * | ||
| 77 | * <p><pre> | ||
| 78 | * U+0066 LATIN SMALL LETTER F | ||
| 79 | * U+0066 LATIN SMALL LETTER F | ||
| 80 | * U+0069 LATIN SMALL LETTER I</pre> | ||
| 81 | * </p> | ||
| 82 | * | ||
| 83 | * or as the single character | ||
| 84 | * | ||
| 85 | * <p><pre> | ||
| 86 | * U+FB03 LATIN SMALL LIGATURE FFI</pre> | ||
| 87 | * </p> | ||
| 88 | * | ||
| 89 | * The ffi ligature is not a distinct semantic character, and strictly speaking | ||
| 90 | * it shouldn't be in Unicode at all, but it was included for compatibility | ||
| 91 | * with existing character sets that already provided it. The Unicode standard | ||
| 92 | * identifies such characters by giving them "compatibility" decompositions | ||
| 93 | * into the corresponding semantic characters. When sorting and searching, you | ||
| 94 | * will often want to use these mappings. | ||
| 95 | * <p> | ||
| 96 | * The <code>normalize</code> method helps solve these problems by transforming | ||
| 97 | * text into the canonical composed and decomposed forms as shown in the first | ||
| 98 | * example above. In addition, you can have it perform compatibility | ||
| 99 | * decompositions so that you can treat compatibility characters the same as | ||
| 100 | * their equivalents. | ||
| 101 | * Finally, the <code>normalize</code> method rearranges accents into the | ||
| 102 | * proper canonical order, so that you do not have to worry about accent | ||
| 103 | * rearrangement on your own. | ||
| 104 | * <p> | ||
| 105 | * The W3C generally recommends to exchange texts in NFC. | ||
| 106 | * Note also that most legacy character encodings use only precomposed forms and | ||
| 107 | * often do not encode any combining marks by themselves. For conversion to such | ||
| 108 | * character encodings the Unicode text needs to be normalized to NFC. | ||
| 109 | * For more usage examples, see the Unicode Standard Annex. | ||
| 110 | * | ||
| 111 | * @since 1.6 | ||
| 112 | */ | ||
| 113 | public final class Normalizer { | ||
| 114 | |||
| 115 | private Normalizer() {}; | ||
| 116 | |||
| 117 | /** | ||
| 118 | * This enum provides constants of the four Unicode normalization forms | ||
| 119 | * that are described in | ||
| 120 | * <a href="http://www.unicode.org/unicode/reports/tr15/tr15-23.html"> | ||
| 121 | * Unicode Standard Annex #15 — Unicode Normalization Forms</a> | ||
| 122 | * and two methods to access them. | ||
| 123 | * | ||
| 124 | * @since 1.6 | ||
| 125 | */ | ||
| 126 | public static enum Form { | ||
| 127 | |||
| 128 | /** | ||
| 129 | * Canonical decomposition. | ||
| 130 | */ | ||
| 131 | NFD, | ||
| 132 | |||
| 133 | /** | ||
| 134 | * Canonical decomposition, followed by canonical composition. | ||
| 135 | */ | ||
| 136 | NFC, | ||
| 137 | |||
| 138 | /** | ||
| 139 | * Compatibility decomposition. | ||
| 140 | */ | ||
| 141 | NFKD, | ||
| 142 | |||
| 143 | /** | ||
| 144 | * Compatibility decomposition, followed by canonical composition. | ||
| 145 | */ | ||
| 146 | NFKC | ||
| 147 | } | ||
| 148 | |||
| 149 | /** | ||
| 150 | * Normalize a sequence of char values. | ||
| 151 | * The sequence will be normalized according to the specified normalization | ||
| 152 | * from. | ||
| 153 | * @param src The sequence of char values to normalize. | ||
| 154 | * @param form The normalization form; one of | ||
| 155 | * {@link java.text.Normalizer.Form#NFC}, | ||
| 156 | * {@link java.text.Normalizer.Form#NFD}, | ||
| 157 | * {@link java.text.Normalizer.Form#NFKC}, | ||
| 158 | * {@link java.text.Normalizer.Form#NFKD} | ||
| 159 | * @return The normalized String | ||
| 160 | * @throws NullPointerException If <code>src</code> or <code>form</code> | ||
| 161 | * is null. | ||
| 162 | */ | ||
| 163 | public static String normalize(CharSequence src, Form form) { | ||
| 164 | return NormalizerBase.normalize(src.toString(), form); | ||
| 165 | } | ||
| 166 | |||
| 167 | /** | ||
| 168 | * Determines if the given sequence of char values is normalized. | ||
| 169 | * @param src The sequence of char values to be checked. | ||
| 170 | * @param form The normalization form; one of | ||
| 171 | * {@link java.text.Normalizer.Form#NFC}, | ||
| 172 | * {@link java.text.Normalizer.Form#NFD}, | ||
| 173 | * {@link java.text.Normalizer.Form#NFKC}, | ||
| 174 | * {@link java.text.Normalizer.Form#NFKD} | ||
| 175 | * @return true if the sequence of char values is normalized; | ||
| 176 | * false otherwise. | ||
| 177 | * @throws NullPointerException If <code>src</code> or <code>form</code> | ||
| 178 | * is null. | ||
| 179 | */ | ||
| 180 | public static boolean isNormalized(CharSequence src, Form form) { | ||
| 181 | return NormalizerBase.isNormalized(src.toString(), form); | ||
| 182 | } | ||
| 183 | } | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,1162 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.io.InvalidObjectException; | ||
| 42 | import java.io.IOException; | ||
| 43 | import java.io.ObjectInputStream; | ||
| 44 | import java.io.ObjectOutputStream; | ||
| 45 | import java.math.BigInteger; | ||
| 46 | import java.math.RoundingMode; | ||
| 47 | import java.text.spi.NumberFormatProvider; | ||
| 48 | import java.util.Currency; | ||
| 49 | import java.util.HashMap; | ||
| 50 | import java.util.Hashtable; | ||
| 51 | import java.util.Locale; | ||
| 52 | import java.util.Map; | ||
| 53 | import java.util.ResourceBundle; | ||
| 54 | import java.util.concurrent.atomic.AtomicInteger; | ||
| 55 | import java.util.concurrent.atomic.AtomicLong; | ||
| 56 | import java.util.spi.LocaleServiceProvider; | ||
| 57 | import sun.util.LocaleServiceProviderPool; | ||
| 58 | import sun.util.resources.LocaleData; | ||
| 59 | |||
| 60 | /** | ||
| 61 | * <code>NumberFormat</code> is the abstract base class for all number | ||
| 62 | * formats. This class provides the interface for formatting and parsing | ||
| 63 | * numbers. <code>NumberFormat</code> also provides methods for determining | ||
| 64 | * which locales have number formats, and what their names are. | ||
| 65 | * | ||
| 66 | * <p> | ||
| 67 | * <code>NumberFormat</code> helps you to format and parse numbers for any locale. | ||
| 68 | * Your code can be completely independent of the locale conventions for | ||
| 69 | * decimal points, thousands-separators, or even the particular decimal | ||
| 70 | * digits used, or whether the number format is even decimal. | ||
| 71 | * | ||
| 72 | * <p> | ||
| 73 | * To format a number for the current Locale, use one of the factory | ||
| 74 | * class methods: | ||
| 75 | * <blockquote> | ||
| 76 | * <pre> | ||
| 77 | * myString = NumberFormat.getInstance().format(myNumber); | ||
| 78 | * </pre> | ||
| 79 | * </blockquote> | ||
| 80 | * If you are formatting multiple numbers, it is | ||
| 81 | * more efficient to get the format and use it multiple times so that | ||
| 82 | * the system doesn't have to fetch the information about the local | ||
| 83 | * language and country conventions multiple times. | ||
| 84 | * <blockquote> | ||
| 85 | * <pre> | ||
| 86 | * NumberFormat nf = NumberFormat.getInstance(); | ||
| 87 | * for (int i = 0; i < myNumber.length; ++i) { | ||
| 88 | * output.println(nf.format(myNumber[i]) + "; "); | ||
| 89 | * } | ||
| 90 | * </pre> | ||
| 91 | * </blockquote> | ||
| 92 | * To format a number for a different Locale, specify it in the | ||
| 93 | * call to <code>getInstance</code>. | ||
| 94 | * <blockquote> | ||
| 95 | * <pre> | ||
| 96 | * NumberFormat nf = NumberFormat.getInstance(Locale.FRENCH); | ||
| 97 | * </pre> | ||
| 98 | * </blockquote> | ||
| 99 | * You can also use a <code>NumberFormat</code> to parse numbers: | ||
| 100 | * <blockquote> | ||
| 101 | * <pre> | ||
| 102 | * myNumber = nf.parse(myString); | ||
| 103 | * </pre> | ||
| 104 | * </blockquote> | ||
| 105 | * Use <code>getInstance</code> or <code>getNumberInstance</code> to get the | ||
| 106 | * normal number format. Use <code>getIntegerInstance</code> to get an | ||
| 107 | * integer number format. Use <code>getCurrencyInstance</code> to get the | ||
| 108 | * currency number format. And use <code>getPercentInstance</code> to get a | ||
| 109 | * format for displaying percentages. With this format, a fraction like | ||
| 110 | * 0.53 is displayed as 53%. | ||
| 111 | * | ||
| 112 | * <p> | ||
| 113 | * You can also control the display of numbers with such methods as | ||
| 114 | * <code>setMinimumFractionDigits</code>. | ||
| 115 | * If you want even more control over the format or parsing, | ||
| 116 | * or want to give your users more control, | ||
| 117 | * you can try casting the <code>NumberFormat</code> you get from the factory methods | ||
| 118 | * to a <code>DecimalFormat</code>. This will work for the vast majority | ||
| 119 | * of locales; just remember to put it in a <code>try</code> block in case you | ||
| 120 | * encounter an unusual one. | ||
| 121 | * | ||
| 122 | * <p> | ||
| 123 | * NumberFormat and DecimalFormat are designed such that some controls | ||
| 124 | * work for formatting and others work for parsing. The following is | ||
| 125 | * the detailed description for each these control methods, | ||
| 126 | * <p> | ||
| 127 | * setParseIntegerOnly : only affects parsing, e.g. | ||
| 128 | * if true, "3456.78" -> 3456 (and leaves the parse position just after index 6) | ||
| 129 | * if false, "3456.78" -> 3456.78 (and leaves the parse position just after index 8) | ||
| 130 | * This is independent of formatting. If you want to not show a decimal point | ||
| 131 | * where there might be no digits after the decimal point, use | ||
| 132 | * setDecimalSeparatorAlwaysShown. | ||
| 133 | * <p> | ||
| 134 | * setDecimalSeparatorAlwaysShown : only affects formatting, and only where | ||
| 135 | * there might be no digits after the decimal point, such as with a pattern | ||
| 136 | * like "#,##0.##", e.g., | ||
| 137 | * if true, 3456.00 -> "3,456." | ||
| 138 | * if false, 3456.00 -> "3456" | ||
| 139 | * This is independent of parsing. If you want parsing to stop at the decimal | ||
| 140 | * point, use setParseIntegerOnly. | ||
| 141 | * | ||
| 142 | * <p> | ||
| 143 | * You can also use forms of the <code>parse</code> and <code>format</code> | ||
| 144 | * methods with <code>ParsePosition</code> and <code>FieldPosition</code> to | ||
| 145 | * allow you to: | ||
| 146 | * <ul> | ||
| 147 | * <li> progressively parse through pieces of a string | ||
| 148 | * <li> align the decimal point and other areas | ||
| 149 | * </ul> | ||
| 150 | * For example, you can align numbers in two ways: | ||
| 151 | * <ol> | ||
| 152 | * <li> If you are using a monospaced font with spacing for alignment, | ||
| 153 | * you can pass the <code>FieldPosition</code> in your format call, with | ||
| 154 | * <code>field</code> = <code>INTEGER_FIELD</code>. On output, | ||
| 155 | * <code>getEndIndex</code> will be set to the offset between the | ||
| 156 | * last character of the integer and the decimal. Add | ||
| 157 | * (desiredSpaceCount - getEndIndex) spaces at the front of the string. | ||
| 158 | * | ||
| 159 | * <li> If you are using proportional fonts, | ||
| 160 | * instead of padding with spaces, measure the width | ||
| 161 | * of the string in pixels from the start to <code>getEndIndex</code>. | ||
| 162 | * Then move the pen by | ||
| 163 | * (desiredPixelWidth - widthToAlignmentPoint) before drawing the text. | ||
| 164 | * It also works where there is no decimal, but possibly additional | ||
| 165 | * characters at the end, e.g., with parentheses in negative | ||
| 166 | * numbers: "(12)" for -12. | ||
| 167 | * </ol> | ||
| 168 | * | ||
| 169 | * <h4><a name="synchronization">Synchronization</a></h4> | ||
| 170 | * | ||
| 171 | * <p> | ||
| 172 | * Number formats are generally not synchronized. | ||
| 173 | * It is recommended to create separate format instances for each thread. | ||
| 174 | * If multiple threads access a format concurrently, it must be synchronized | ||
| 175 | * externally. | ||
| 176 | * | ||
| 177 | * @see DecimalFormat | ||
| 178 | * @see ChoiceFormat | ||
| 179 | * @author Mark Davis | ||
| 180 | * @author Helena Shih | ||
| 181 | */ | ||
| 182 | public abstract class NumberFormat extends Format { | ||
| 183 | |||
| 184 | /** | ||
| 185 | * Field constant used to construct a FieldPosition object. Signifies that | ||
| 186 | * the position of the integer part of a formatted number should be returned. | ||
| 187 | * @see java.text.FieldPosition | ||
| 188 | */ | ||
| 189 | public static final int INTEGER_FIELD = 0; | ||
| 190 | |||
| 191 | /** | ||
| 192 | * Field constant used to construct a FieldPosition object. Signifies that | ||
| 193 | * the position of the fraction part of a formatted number should be returned. | ||
| 194 | * @see java.text.FieldPosition | ||
| 195 | */ | ||
| 196 | public static final int FRACTION_FIELD = 1; | ||
| 197 | |||
| 198 | /** | ||
| 199 | * Sole constructor. (For invocation by subclass constructors, typically | ||
| 200 | * implicit.) | ||
| 201 | */ | ||
| 202 | protected NumberFormat() { | ||
| 203 | } | ||
| 204 | |||
| 205 | /** | ||
| 206 | * Formats a number and appends the resulting text to the given string | ||
| 207 | * buffer. | ||
| 208 | * The number can be of any subclass of {@link java.lang.Number}. | ||
| 209 | * <p> | ||
| 210 | * This implementation extracts the number's value using | ||
| 211 | * {@link java.lang.Number#longValue()} for all integral type values that | ||
| 212 | * can be converted to <code>long</code> without loss of information, | ||
| 213 | * including <code>BigInteger</code> values with a | ||
| 214 | * {@link java.math.BigInteger#bitLength() bit length} of less than 64, | ||
| 215 | * and {@link java.lang.Number#doubleValue()} for all other types. It | ||
| 216 | * then calls | ||
| 217 | * {@link #format(long,java.lang.StringBuffer,java.text.FieldPosition)} | ||
| 218 | * or {@link #format(double,java.lang.StringBuffer,java.text.FieldPosition)}. | ||
| 219 | * This may result in loss of magnitude information and precision for | ||
| 220 | * <code>BigInteger</code> and <code>BigDecimal</code> values. | ||
| 221 | * @param number the number to format | ||
| 222 | * @param toAppendTo the <code>StringBuffer</code> to which the formatted | ||
| 223 | * text is to be appended | ||
| 224 | * @param pos On input: an alignment field, if desired. | ||
| 225 | * On output: the offsets of the alignment field. | ||
| 226 | * @return the value passed in as <code>toAppendTo</code> | ||
| 227 | * @exception IllegalArgumentException if <code>number</code> is | ||
| 228 | * null or not an instance of <code>Number</code>. | ||
| 229 | * @exception NullPointerException if <code>toAppendTo</code> or | ||
| 230 | * <code>pos</code> is null | ||
| 231 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 232 | * mode being set to RoundingMode.UNNECESSARY | ||
| 233 | * @see java.text.FieldPosition | ||
| 234 | */ | ||
| 235 | public StringBuffer format(Object number, | ||
| 236 | StringBuffer toAppendTo, | ||
| 237 | FieldPosition pos) { | ||
| 238 | if (number instanceof Long || number instanceof Integer || | ||
| 239 | number instanceof Short || number instanceof Byte || | ||
| 240 | number instanceof AtomicInteger || number instanceof AtomicLong || | ||
| 241 | (number instanceof BigInteger && | ||
| 242 | ((BigInteger)number).bitLength() < 64)) { | ||
| 243 | return format(((Number)number).longValue(), toAppendTo, pos); | ||
| 244 | } else if (number instanceof Number) { | ||
| 245 | return format(((Number)number).doubleValue(), toAppendTo, pos); | ||
| 246 | } else { | ||
| 247 | throw new IllegalArgumentException("Cannot format given Object as a Number"); | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | /** | ||
| 252 | * Parses text from a string to produce a <code>Number</code>. | ||
| 253 | * <p> | ||
| 254 | * The method attempts to parse text starting at the index given by | ||
| 255 | * <code>pos</code>. | ||
| 256 | * If parsing succeeds, then the index of <code>pos</code> is updated | ||
| 257 | * to the index after the last character used (parsing does not necessarily | ||
| 258 | * use all characters up to the end of the string), and the parsed | ||
| 259 | * number is returned. The updated <code>pos</code> can be used to | ||
| 260 | * indicate the starting point for the next call to this method. | ||
| 261 | * If an error occurs, then the index of <code>pos</code> is not | ||
| 262 | * changed, the error index of <code>pos</code> is set to the index of | ||
| 263 | * the character where the error occurred, and null is returned. | ||
| 264 | * <p> | ||
| 265 | * See the {@link #parse(String, ParsePosition)} method for more information | ||
| 266 | * on number parsing. | ||
| 267 | * | ||
| 268 | * @param source A <code>String</code>, part of which should be parsed. | ||
| 269 | * @param pos A <code>ParsePosition</code> object with index and error | ||
| 270 | * index information as described above. | ||
| 271 | * @return A <code>Number</code> parsed from the string. In case of | ||
| 272 | * error, returns null. | ||
| 273 | * @exception NullPointerException if <code>pos</code> is null. | ||
| 274 | */ | ||
| 275 | public final Object parseObject(String source, ParsePosition pos) { | ||
| 276 | return parse(source, pos); | ||
| 277 | } | ||
| 278 | |||
| 279 | /** | ||
| 280 | * Specialization of format. | ||
| 281 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 282 | * mode being set to RoundingMode.UNNECESSARY | ||
| 283 | * @see java.text.Format#format | ||
| 284 | */ | ||
| 285 | public final String format(double number) { | ||
| 286 | return format(number, new StringBuffer(), | ||
| 287 | DontCareFieldPosition.INSTANCE).toString(); | ||
| 288 | } | ||
| 289 | |||
| 290 | /** | ||
| 291 | * Specialization of format. | ||
| 292 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 293 | * mode being set to RoundingMode.UNNECESSARY | ||
| 294 | * @see java.text.Format#format | ||
| 295 | */ | ||
| 296 | public final String format(long number) { | ||
| 297 | return format(number, new StringBuffer(), | ||
| 298 | DontCareFieldPosition.INSTANCE).toString(); | ||
| 299 | } | ||
| 300 | |||
| 301 | /** | ||
| 302 | * Specialization of format. | ||
| 303 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 304 | * mode being set to RoundingMode.UNNECESSARY | ||
| 305 | * @see java.text.Format#format | ||
| 306 | */ | ||
| 307 | public abstract StringBuffer format(double number, | ||
| 308 | StringBuffer toAppendTo, | ||
| 309 | FieldPosition pos); | ||
| 310 | |||
| 311 | /** | ||
| 312 | * Specialization of format. | ||
| 313 | * @exception ArithmeticException if rounding is needed with rounding | ||
| 314 | * mode being set to RoundingMode.UNNECESSARY | ||
| 315 | * @see java.text.Format#format | ||
| 316 | */ | ||
| 317 | public abstract StringBuffer format(long number, | ||
| 318 | StringBuffer toAppendTo, | ||
| 319 | FieldPosition pos); | ||
| 320 | |||
| 321 | /** | ||
| 322 | * Returns a Long if possible (e.g., within the range [Long.MIN_VALUE, | ||
| 323 | * Long.MAX_VALUE] and with no decimals), otherwise a Double. | ||
| 324 | * If IntegerOnly is set, will stop at a decimal | ||
| 325 | * point (or equivalent; e.g., for rational numbers "1 2/3", will stop | ||
| 326 | * after the 1). | ||
| 327 | * Does not throw an exception; if no object can be parsed, index is | ||
| 328 | * unchanged! | ||
| 329 | * @see java.text.NumberFormat#isParseIntegerOnly | ||
| 330 | * @see java.text.Format#parseObject | ||
| 331 | */ | ||
| 332 | public abstract Number parse(String source, ParsePosition parsePosition); | ||
| 333 | |||
| 334 | /** | ||
| 335 | * Parses text from the beginning of the given string to produce a number. | ||
| 336 | * The method may not use the entire text of the given string. | ||
| 337 | * <p> | ||
| 338 | * See the {@link #parse(String, ParsePosition)} method for more information | ||
| 339 | * on number parsing. | ||
| 340 | * | ||
| 341 | * @param source A <code>String</code> whose beginning should be parsed. | ||
| 342 | * @return A <code>Number</code> parsed from the string. | ||
| 343 | * @exception ParseException if the beginning of the specified string | ||
| 344 | * cannot be parsed. | ||
| 345 | */ | ||
| 346 | public Number parse(String source) throws ParseException { | ||
| 347 | ParsePosition parsePosition = new ParsePosition(0); | ||
| 348 | Number result = parse(source, parsePosition); | ||
| 349 | if (parsePosition.index == 0) { | ||
| 350 | throw new ParseException("Unparseable number: \"" + source + "\"", | ||
| 351 | parsePosition.errorIndex); | ||
| 352 | } | ||
| 353 | return result; | ||
| 354 | } | ||
| 355 | |||
| 356 | /** | ||
| 357 | * Returns true if this format will parse numbers as integers only. | ||
| 358 | * For example in the English locale, with ParseIntegerOnly true, the | ||
| 359 | * string "1234." would be parsed as the integer value 1234 and parsing | ||
| 360 | * would stop at the "." character. Of course, the exact format accepted | ||
| 361 | * by the parse operation is locale dependant and determined by sub-classes | ||
| 362 | * of NumberFormat. | ||
| 363 | */ | ||
| 364 | public boolean isParseIntegerOnly() { | ||
| 365 | return parseIntegerOnly; | ||
| 366 | } | ||
| 367 | |||
| 368 | /** | ||
| 369 | * Sets whether or not numbers should be parsed as integers only. | ||
| 370 | * @see #isParseIntegerOnly | ||
| 371 | */ | ||
| 372 | public void setParseIntegerOnly(boolean value) { | ||
| 373 | parseIntegerOnly = value; | ||
| 374 | } | ||
| 375 | |||
| 376 | //============== Locale Stuff ===================== | ||
| 377 | |||
| 378 | /** | ||
| 379 | * Returns a general-purpose number format for the current default locale. | ||
| 380 | * This is the same as calling | ||
| 381 | * {@link #getNumberInstance() getNumberInstance()}. | ||
| 382 | */ | ||
| 383 | public final static NumberFormat getInstance() { | ||
| 384 | return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE); | ||
| 385 | } | ||
| 386 | |||
| 387 | /** | ||
| 388 | * Returns a general-purpose number format for the specified locale. | ||
| 389 | * This is the same as calling | ||
| 390 | * {@link #getNumberInstance(java.util.Locale) getNumberInstance(inLocale)}. | ||
| 391 | */ | ||
| 392 | public static NumberFormat getInstance(Locale inLocale) { | ||
| 393 | return getInstance(inLocale, NUMBERSTYLE); | ||
| 394 | } | ||
| 395 | |||
| 396 | /** | ||
| 397 | * Returns a general-purpose number format for the current default locale. | ||
| 398 | */ | ||
| 399 | public final static NumberFormat getNumberInstance() { | ||
| 400 | return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE); | ||
| 401 | } | ||
| 402 | |||
| 403 | /** | ||
| 404 | * Returns a general-purpose number format for the specified locale. | ||
| 405 | */ | ||
| 406 | public static NumberFormat getNumberInstance(Locale inLocale) { | ||
| 407 | return getInstance(inLocale, NUMBERSTYLE); | ||
| 408 | } | ||
| 409 | |||
| 410 | /** | ||
| 411 | * Returns an integer number format for the current default locale. The | ||
| 412 | * returned number format is configured to round floating point numbers | ||
| 413 | * to the nearest integer using half-even rounding (see {@link | ||
| 414 | * java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}) for formatting, | ||
| 415 | * and to parse only the integer part of an input string (see {@link | ||
| 416 | * #isParseIntegerOnly isParseIntegerOnly}). | ||
| 417 | * | ||
| 418 | * @see #getRoundingMode() | ||
| 419 | * @return a number format for integer values | ||
| 420 | * @since 1.4 | ||
| 421 | */ | ||
| 422 | public final static NumberFormat getIntegerInstance() { | ||
| 423 | return getInstance(Locale.getDefault(Locale.Category.FORMAT), INTEGERSTYLE); | ||
| 424 | } | ||
| 425 | |||
| 426 | /** | ||
| 427 | * Returns an integer number format for the specified locale. The | ||
| 428 | * returned number format is configured to round floating point numbers | ||
| 429 | * to the nearest integer using half-even rounding (see {@link | ||
| 430 | * java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}) for formatting, | ||
| 431 | * and to parse only the integer part of an input string (see {@link | ||
| 432 | * #isParseIntegerOnly isParseIntegerOnly}). | ||
| 433 | * | ||
| 434 | * @see #getRoundingMode() | ||
| 435 | * @return a number format for integer values | ||
| 436 | * @since 1.4 | ||
| 437 | */ | ||
| 438 | public static NumberFormat getIntegerInstance(Locale inLocale) { | ||
| 439 | return getInstance(inLocale, INTEGERSTYLE); | ||
| 440 | } | ||
| 441 | |||
| 442 | /** | ||
| 443 | * Returns a currency format for the current default locale. | ||
| 444 | */ | ||
| 445 | public final static NumberFormat getCurrencyInstance() { | ||
| 446 | return getInstance(Locale.getDefault(Locale.Category.FORMAT), CURRENCYSTYLE); | ||
| 447 | } | ||
| 448 | |||
| 449 | /** | ||
| 450 | * Returns a currency format for the specified locale. | ||
| 451 | */ | ||
| 452 | public static NumberFormat getCurrencyInstance(Locale inLocale) { | ||
| 453 | return getInstance(inLocale, CURRENCYSTYLE); | ||
| 454 | } | ||
| 455 | |||
| 456 | /** | ||
| 457 | * Returns a percentage format for the current default locale. | ||
| 458 | */ | ||
| 459 | public final static NumberFormat getPercentInstance() { | ||
| 460 | return getInstance(Locale.getDefault(Locale.Category.FORMAT), PERCENTSTYLE); | ||
| 461 | } | ||
| 462 | |||
| 463 | /** | ||
| 464 | * Returns a percentage format for the specified locale. | ||
| 465 | */ | ||
| 466 | public static NumberFormat getPercentInstance(Locale inLocale) { | ||
| 467 | return getInstance(inLocale, PERCENTSTYLE); | ||
| 468 | } | ||
| 469 | |||
| 470 | /** | ||
| 471 | * Returns a scientific format for the current default locale. | ||
| 472 | */ | ||
| 473 | /*public*/ final static NumberFormat getScientificInstance() { | ||
| 474 | return getInstance(Locale.getDefault(Locale.Category.FORMAT), SCIENTIFICSTYLE); | ||
| 475 | } | ||
| 476 | |||
| 477 | /** | ||
| 478 | * Returns a scientific format for the specified locale. | ||
| 479 | */ | ||
| 480 | /*public*/ static NumberFormat getScientificInstance(Locale inLocale) { | ||
| 481 | return getInstance(inLocale, SCIENTIFICSTYLE); | ||
| 482 | } | ||
| 483 | |||
| 484 | /** | ||
| 485 | * Returns an array of all locales for which the | ||
| 486 | * <code>get*Instance</code> methods of this class can return | ||
| 487 | * localized instances. | ||
| 488 | * The returned array represents the union of locales supported by the Java | ||
| 489 | * runtime and by installed | ||
| 490 | * {@link java.text.spi.NumberFormatProvider NumberFormatProvider} implementations. | ||
| 491 | * It must contain at least a <code>Locale</code> instance equal to | ||
| 492 | * {@link java.util.Locale#US Locale.US}. | ||
| 493 | * | ||
| 494 | * @return An array of locales for which localized | ||
| 495 | * <code>NumberFormat</code> instances are available. | ||
| 496 | */ | ||
| 497 | public static Locale[] getAvailableLocales() { | ||
| 498 | LocaleServiceProviderPool pool = | ||
| 499 | LocaleServiceProviderPool.getPool(NumberFormatProvider.class); | ||
| 500 | return pool.getAvailableLocales(); | ||
| 501 | } | ||
| 502 | |||
| 503 | /** | ||
| 504 | * Overrides hashCode | ||
| 505 | */ | ||
| 506 | public int hashCode() { | ||
| 507 | return maximumIntegerDigits * 37 + maxFractionDigits; | ||
| 508 | // just enough fields for a reasonable distribution | ||
| 509 | } | ||
| 510 | |||
| 511 | /** | ||
| 512 | * Overrides equals | ||
| 513 | */ | ||
| 514 | public boolean equals(Object obj) { | ||
| 515 | if (obj == null) { | ||
| 516 | return false; | ||
| 517 | } | ||
| 518 | if (this == obj) { | ||
| 519 | return true; | ||
| 520 | } | ||
| 521 | if (getClass() != obj.getClass()) { | ||
| 522 | return false; | ||
| 523 | } | ||
| 524 | NumberFormat other = (NumberFormat) obj; | ||
| 525 | return (maximumIntegerDigits == other.maximumIntegerDigits | ||
| 526 | && minimumIntegerDigits == other.minimumIntegerDigits | ||
| 527 | && maximumFractionDigits == other.maximumFractionDigits | ||
| 528 | && minimumFractionDigits == other.minimumFractionDigits | ||
| 529 | && groupingUsed == other.groupingUsed | ||
| 530 | && parseIntegerOnly == other.parseIntegerOnly); | ||
| 531 | } | ||
| 532 | |||
| 533 | /** | ||
| 534 | * Overrides Cloneable | ||
| 535 | */ | ||
| 536 | public Object clone() { | ||
| 537 | NumberFormat other = (NumberFormat) super.clone(); | ||
| 538 | return other; | ||
| 539 | } | ||
| 540 | |||
| 541 | /** | ||
| 542 | * Returns true if grouping is used in this format. For example, in the | ||
| 543 | * English locale, with grouping on, the number 1234567 might be formatted | ||
| 544 | * as "1,234,567". The grouping separator as well as the size of each group | ||
| 545 | * is locale dependant and is determined by sub-classes of NumberFormat. | ||
| 546 | * @see #setGroupingUsed | ||
| 547 | */ | ||
| 548 | public boolean isGroupingUsed() { | ||
| 549 | return groupingUsed; | ||
| 550 | } | ||
| 551 | |||
| 552 | /** | ||
| 553 | * Set whether or not grouping will be used in this format. | ||
| 554 | * @see #isGroupingUsed | ||
| 555 | */ | ||
| 556 | public void setGroupingUsed(boolean newValue) { | ||
| 557 | groupingUsed = newValue; | ||
| 558 | } | ||
| 559 | |||
| 560 | /** | ||
| 561 | * Returns the maximum number of digits allowed in the integer portion of a | ||
| 562 | * number. | ||
| 563 | * @see #setMaximumIntegerDigits | ||
| 564 | */ | ||
| 565 | public int getMaximumIntegerDigits() { | ||
| 566 | return maximumIntegerDigits; | ||
| 567 | } | ||
| 568 | |||
| 569 | /** | ||
| 570 | * Sets the maximum number of digits allowed in the integer portion of a | ||
| 571 | * number. maximumIntegerDigits must be >= minimumIntegerDigits. If the | ||
| 572 | * new value for maximumIntegerDigits is less than the current value | ||
| 573 | * of minimumIntegerDigits, then minimumIntegerDigits will also be set to | ||
| 574 | * the new value. | ||
| 575 | * @param newValue the maximum number of integer digits to be shown; if | ||
| 576 | * less than zero, then zero is used. The concrete subclass may enforce an | ||
| 577 | * upper limit to this value appropriate to the numeric type being formatted. | ||
| 578 | * @see #getMaximumIntegerDigits | ||
| 579 | */ | ||
| 580 | public void setMaximumIntegerDigits(int newValue) { | ||
| 581 | maximumIntegerDigits = Math.max(0,newValue); | ||
| 582 | if (minimumIntegerDigits > maximumIntegerDigits) { | ||
| 583 | minimumIntegerDigits = maximumIntegerDigits; | ||
| 584 | } | ||
| 585 | } | ||
| 586 | |||
| 587 | /** | ||
| 588 | * Returns the minimum number of digits allowed in the integer portion of a | ||
| 589 | * number. | ||
| 590 | * @see #setMinimumIntegerDigits | ||
| 591 | */ | ||
| 592 | public int getMinimumIntegerDigits() { | ||
| 593 | return minimumIntegerDigits; | ||
| 594 | } | ||
| 595 | |||
| 596 | /** | ||
| 597 | * Sets the minimum number of digits allowed in the integer portion of a | ||
| 598 | * number. minimumIntegerDigits must be <= maximumIntegerDigits. If the | ||
| 599 | * new value for minimumIntegerDigits exceeds the current value | ||
| 600 | * of maximumIntegerDigits, then maximumIntegerDigits will also be set to | ||
| 601 | * the new value | ||
| 602 | * @param newValue the minimum number of integer digits to be shown; if | ||
| 603 | * less than zero, then zero is used. The concrete subclass may enforce an | ||
| 604 | * upper limit to this value appropriate to the numeric type being formatted. | ||
| 605 | * @see #getMinimumIntegerDigits | ||
| 606 | */ | ||
| 607 | public void setMinimumIntegerDigits(int newValue) { | ||
| 608 | minimumIntegerDigits = Math.max(0,newValue); | ||
| 609 | if (minimumIntegerDigits > maximumIntegerDigits) { | ||
| 610 | maximumIntegerDigits = minimumIntegerDigits; | ||
| 611 | } | ||
| 612 | } | ||
| 613 | |||
| 614 | /** | ||
| 615 | * Returns the maximum number of digits allowed in the fraction portion of a | ||
| 616 | * number. | ||
| 617 | * @see #setMaximumFractionDigits | ||
| 618 | */ | ||
| 619 | public int getMaximumFractionDigits() { | ||
| 620 | return maximumFractionDigits; | ||
| 621 | } | ||
| 622 | |||
| 623 | /** | ||
| 624 | * Sets the maximum number of digits allowed in the fraction portion of a | ||
| 625 | * number. maximumFractionDigits must be >= minimumFractionDigits. If the | ||
| 626 | * new value for maximumFractionDigits is less than the current value | ||
| 627 | * of minimumFractionDigits, then minimumFractionDigits will also be set to | ||
| 628 | * the new value. | ||
| 629 | * @param newValue the maximum number of fraction digits to be shown; if | ||
| 630 | * less than zero, then zero is used. The concrete subclass may enforce an | ||
| 631 | * upper limit to this value appropriate to the numeric type being formatted. | ||
| 632 | * @see #getMaximumFractionDigits | ||
| 633 | */ | ||
| 634 | public void setMaximumFractionDigits(int newValue) { | ||
| 635 | maximumFractionDigits = Math.max(0,newValue); | ||
| 636 | if (maximumFractionDigits < minimumFractionDigits) { | ||
| 637 | minimumFractionDigits = maximumFractionDigits; | ||
| 638 | } | ||
| 639 | } | ||
| 640 | |||
| 641 | /** | ||
| 642 | * Returns the minimum number of digits allowed in the fraction portion of a | ||
| 643 | * number. | ||
| 644 | * @see #setMinimumFractionDigits | ||
| 645 | */ | ||
| 646 | public int getMinimumFractionDigits() { | ||
| 647 | return minimumFractionDigits; | ||
| 648 | } | ||
| 649 | |||
| 650 | /** | ||
| 651 | * Sets the minimum number of digits allowed in the fraction portion of a | ||
| 652 | * number. minimumFractionDigits must be <= maximumFractionDigits. If the | ||
| 653 | * new value for minimumFractionDigits exceeds the current value | ||
| 654 | * of maximumFractionDigits, then maximumIntegerDigits will also be set to | ||
| 655 | * the new value | ||
| 656 | * @param newValue the minimum number of fraction digits to be shown; if | ||
| 657 | * less than zero, then zero is used. The concrete subclass may enforce an | ||
| 658 | * upper limit to this value appropriate to the numeric type being formatted. | ||
| 659 | * @see #getMinimumFractionDigits | ||
| 660 | */ | ||
| 661 | public void setMinimumFractionDigits(int newValue) { | ||
| 662 | minimumFractionDigits = Math.max(0,newValue); | ||
| 663 | if (maximumFractionDigits < minimumFractionDigits) { | ||
| 664 | maximumFractionDigits = minimumFractionDigits; | ||
| 665 | } | ||
| 666 | } | ||
| 667 | |||
| 668 | /** | ||
| 669 | * Gets the currency used by this number format when formatting | ||
| 670 | * currency values. The initial value is derived in a locale dependent | ||
| 671 | * way. The returned value may be null if no valid | ||
| 672 | * currency could be determined and no currency has been set using | ||
| 673 | * {@link #setCurrency(java.util.Currency) setCurrency}. | ||
| 674 | * <p> | ||
| 675 | * The default implementation throws | ||
| 676 | * <code>UnsupportedOperationException</code>. | ||
| 677 | * | ||
| 678 | * @return the currency used by this number format, or <code>null</code> | ||
| 679 | * @exception UnsupportedOperationException if the number format class | ||
| 680 | * doesn't implement currency formatting | ||
| 681 | * @since 1.4 | ||
| 682 | */ | ||
| 683 | public Currency getCurrency() { | ||
| 684 | throw new UnsupportedOperationException(); | ||
| 685 | } | ||
| 686 | |||
| 687 | /** | ||
| 688 | * Sets the currency used by this number format when formatting | ||
| 689 | * currency values. This does not update the minimum or maximum | ||
| 690 | * number of fraction digits used by the number format. | ||
| 691 | * <p> | ||
| 692 | * The default implementation throws | ||
| 693 | * <code>UnsupportedOperationException</code>. | ||
| 694 | * | ||
| 695 | * @param currency the new currency to be used by this number format | ||
| 696 | * @exception UnsupportedOperationException if the number format class | ||
| 697 | * doesn't implement currency formatting | ||
| 698 | * @exception NullPointerException if <code>currency</code> is null | ||
| 699 | * @since 1.4 | ||
| 700 | */ | ||
| 701 | public void setCurrency(Currency currency) { | ||
| 702 | throw new UnsupportedOperationException(); | ||
| 703 | } | ||
| 704 | |||
| 705 | /** | ||
| 706 | * Gets the {@link java.math.RoundingMode} used in this NumberFormat. | ||
| 707 | * The default implementation of this method in NumberFormat | ||
| 708 | * always throws {@link java.lang.UnsupportedOperationException}. | ||
| 709 | * Subclasses which handle different rounding modes should override | ||
| 710 | * this method. | ||
| 711 | * | ||
| 712 | * @exception UnsupportedOperationException The default implementation | ||
| 713 | * always throws this exception | ||
| 714 | * @return The <code>RoundingMode</code> used for this NumberFormat. | ||
| 715 | * @see #setRoundingMode(RoundingMode) | ||
| 716 | * @since 1.6 | ||
| 717 | */ | ||
| 718 | public RoundingMode getRoundingMode() { | ||
| 719 | throw new UnsupportedOperationException(); | ||
| 720 | } | ||
| 721 | |||
| 722 | /** | ||
| 723 | * Sets the {@link java.math.RoundingMode} used in this NumberFormat. | ||
| 724 | * The default implementation of this method in NumberFormat always | ||
| 725 | * throws {@link java.lang.UnsupportedOperationException}. | ||
| 726 | * Subclasses which handle different rounding modes should override | ||
| 727 | * this method. | ||
| 728 | * | ||
| 729 | * @exception UnsupportedOperationException The default implementation | ||
| 730 | * always throws this exception | ||
| 731 | * @exception NullPointerException if <code>roundingMode</code> is null | ||
| 732 | * @param roundingMode The <code>RoundingMode</code> to be used | ||
| 733 | * @see #getRoundingMode() | ||
| 734 | * @since 1.6 | ||
| 735 | */ | ||
| 736 | public void setRoundingMode(RoundingMode roundingMode) { | ||
| 737 | throw new UnsupportedOperationException(); | ||
| 738 | } | ||
| 739 | |||
| 740 | // =======================privates=============================== | ||
| 741 | |||
| 742 | private static NumberFormat getInstance(Locale desiredLocale, | ||
| 743 | int choice) { | ||
| 744 | // Check whether a provider can provide an implementation that's closer | ||
| 745 | // to the requested locale than what the Java runtime itself can provide. | ||
| 746 | LocaleServiceProviderPool pool = | ||
| 747 | LocaleServiceProviderPool.getPool(NumberFormatProvider.class); | ||
| 748 | if (pool.hasProviders()) { | ||
| 749 | NumberFormat providersInstance = pool.getLocalizedObject( | ||
| 750 | NumberFormatGetter.INSTANCE, | ||
| 751 | desiredLocale, | ||
| 752 | choice); | ||
| 753 | if (providersInstance != null) { | ||
| 754 | return providersInstance; | ||
| 755 | } | ||
| 756 | } | ||
| 757 | |||
| 758 | /* try the cache first */ | ||
| 759 | String[] numberPatterns = (String[])cachedLocaleData.get(desiredLocale); | ||
| 760 | if (numberPatterns == null) { /* cache miss */ | ||
| 761 | ResourceBundle resource = LocaleData.getNumberFormatData(desiredLocale); | ||
| 762 | numberPatterns = resource.getStringArray("NumberPatterns"); | ||
| 763 | /* update cache */ | ||
| 764 | cachedLocaleData.put(desiredLocale, numberPatterns); | ||
| 765 | } | ||
| 766 | |||
| 767 | DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(desiredLocale); | ||
| 768 | int entry = (choice == INTEGERSTYLE) ? NUMBERSTYLE : choice; | ||
| 769 | DecimalFormat format = new DecimalFormat(numberPatterns[entry], symbols); | ||
| 770 | |||
| 771 | if (choice == INTEGERSTYLE) { | ||
| 772 | format.setMaximumFractionDigits(0); | ||
| 773 | format.setDecimalSeparatorAlwaysShown(false); | ||
| 774 | format.setParseIntegerOnly(true); | ||
| 775 | } else if (choice == CURRENCYSTYLE) { | ||
| 776 | format.adjustForCurrencyDefaultFractionDigits(); | ||
| 777 | } | ||
| 778 | |||
| 779 | return format; | ||
| 780 | } | ||
| 781 | |||
| 782 | /** | ||
| 783 | * First, read in the default serializable data. | ||
| 784 | * | ||
| 785 | * Then, if <code>serialVersionOnStream</code> is less than 1, indicating that | ||
| 786 | * the stream was written by JDK 1.1, | ||
| 787 | * set the <code>int</code> fields such as <code>maximumIntegerDigits</code> | ||
| 788 | * to be equal to the <code>byte</code> fields such as <code>maxIntegerDigits</code>, | ||
| 789 | * since the <code>int</code> fields were not present in JDK 1.1. | ||
| 790 | * Finally, set serialVersionOnStream back to the maximum allowed value so that | ||
| 791 | * default serialization will work properly if this object is streamed out again. | ||
| 792 | * | ||
| 793 | * <p>If <code>minimumIntegerDigits</code> is greater than | ||
| 794 | * <code>maximumIntegerDigits</code> or <code>minimumFractionDigits</code> | ||
| 795 | * is greater than <code>maximumFractionDigits</code>, then the stream data | ||
| 796 | * is invalid and this method throws an <code>InvalidObjectException</code>. | ||
| 797 | * In addition, if any of these values is negative, then this method throws | ||
| 798 | * an <code>InvalidObjectException</code>. | ||
| 799 | * | ||
| 800 | * @since 1.2 | ||
| 801 | */ | ||
| 802 | private void readObject(ObjectInputStream stream) | ||
| 803 | throws IOException, ClassNotFoundException | ||
| 804 | { | ||
| 805 | stream.defaultReadObject(); | ||
| 806 | if (serialVersionOnStream < 1) { | ||
| 807 | // Didn't have additional int fields, reassign to use them. | ||
| 808 | maximumIntegerDigits = maxIntegerDigits; | ||
| 809 | minimumIntegerDigits = minIntegerDigits; | ||
| 810 | maximumFractionDigits = maxFractionDigits; | ||
| 811 | minimumFractionDigits = minFractionDigits; | ||
| 812 | } | ||
| 813 | if (minimumIntegerDigits > maximumIntegerDigits || | ||
| 814 | minimumFractionDigits > maximumFractionDigits || | ||
| 815 | minimumIntegerDigits < 0 || minimumFractionDigits < 0) { | ||
| 816 | throw new InvalidObjectException("Digit count range invalid"); | ||
| 817 | } | ||
| 818 | serialVersionOnStream = currentSerialVersion; | ||
| 819 | } | ||
| 820 | |||
| 821 | /** | ||
| 822 | * Write out the default serializable data, after first setting | ||
| 823 | * the <code>byte</code> fields such as <code>maxIntegerDigits</code> to be | ||
| 824 | * equal to the <code>int</code> fields such as <code>maximumIntegerDigits</code> | ||
| 825 | * (or to <code>Byte.MAX_VALUE</code>, whichever is smaller), for compatibility | ||
| 826 | * with the JDK 1.1 version of the stream format. | ||
| 827 | * | ||
| 828 | * @since 1.2 | ||
| 829 | */ | ||
| 830 | private void writeObject(ObjectOutputStream stream) | ||
| 831 | throws IOException | ||
| 832 | { | ||
| 833 | maxIntegerDigits = (maximumIntegerDigits > Byte.MAX_VALUE) ? | ||
| 834 | Byte.MAX_VALUE : (byte)maximumIntegerDigits; | ||
| 835 | minIntegerDigits = (minimumIntegerDigits > Byte.MAX_VALUE) ? | ||
| 836 | Byte.MAX_VALUE : (byte)minimumIntegerDigits; | ||
| 837 | maxFractionDigits = (maximumFractionDigits > Byte.MAX_VALUE) ? | ||
| 838 | Byte.MAX_VALUE : (byte)maximumFractionDigits; | ||
| 839 | minFractionDigits = (minimumFractionDigits > Byte.MAX_VALUE) ? | ||
| 840 | Byte.MAX_VALUE : (byte)minimumFractionDigits; | ||
| 841 | stream.defaultWriteObject(); | ||
| 842 | } | ||
| 843 | |||
| 844 | /** | ||
| 845 | * Cache to hold the NumberPatterns of a Locale. | ||
| 846 | */ | ||
| 847 | private static final Hashtable cachedLocaleData = new Hashtable(3); | ||
| 848 | |||
| 849 | // Constants used by factory methods to specify a style of format. | ||
| 850 | private static final int NUMBERSTYLE = 0; | ||
| 851 | private static final int CURRENCYSTYLE = 1; | ||
| 852 | private static final int PERCENTSTYLE = 2; | ||
| 853 | private static final int SCIENTIFICSTYLE = 3; | ||
| 854 | private static final int INTEGERSTYLE = 4; | ||
| 855 | |||
| 856 | /** | ||
| 857 | * True if the grouping (i.e. thousands) separator is used when | ||
| 858 | * formatting and parsing numbers. | ||
| 859 | * | ||
| 860 | * @serial | ||
| 861 | * @see #isGroupingUsed | ||
| 862 | */ | ||
| 863 | private boolean groupingUsed = true; | ||
| 864 | |||
| 865 | /** | ||
| 866 | * The maximum number of digits allowed in the integer portion of a | ||
| 867 | * number. <code>maxIntegerDigits</code> must be greater than or equal to | ||
| 868 | * <code>minIntegerDigits</code>. | ||
| 869 | * <p> | ||
| 870 | * <strong>Note:</strong> This field exists only for serialization | ||
| 871 | * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new | ||
| 872 | * <code>int</code> field <code>maximumIntegerDigits</code> is used instead. | ||
| 873 | * When writing to a stream, <code>maxIntegerDigits</code> is set to | ||
| 874 | * <code>maximumIntegerDigits</code> or <code>Byte.MAX_VALUE</code>, | ||
| 875 | * whichever is smaller. When reading from a stream, this field is used | ||
| 876 | * only if <code>serialVersionOnStream</code> is less than 1. | ||
| 877 | * | ||
| 878 | * @serial | ||
| 879 | * @see #getMaximumIntegerDigits | ||
| 880 | */ | ||
| 881 | private byte maxIntegerDigits = 40; | ||
| 882 | |||
| 883 | /** | ||
| 884 | * The minimum number of digits allowed in the integer portion of a | ||
| 885 | * number. <code>minimumIntegerDigits</code> must be less than or equal to | ||
| 886 | * <code>maximumIntegerDigits</code>. | ||
| 887 | * <p> | ||
| 888 | * <strong>Note:</strong> This field exists only for serialization | ||
| 889 | * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new | ||
| 890 | * <code>int</code> field <code>minimumIntegerDigits</code> is used instead. | ||
| 891 | * When writing to a stream, <code>minIntegerDigits</code> is set to | ||
| 892 | * <code>minimumIntegerDigits</code> or <code>Byte.MAX_VALUE</code>, | ||
| 893 | * whichever is smaller. When reading from a stream, this field is used | ||
| 894 | * only if <code>serialVersionOnStream</code> is less than 1. | ||
| 895 | * | ||
| 896 | * @serial | ||
| 897 | * @see #getMinimumIntegerDigits | ||
| 898 | */ | ||
| 899 | private byte minIntegerDigits = 1; | ||
| 900 | |||
| 901 | /** | ||
| 902 | * The maximum number of digits allowed in the fractional portion of a | ||
| 903 | * number. <code>maximumFractionDigits</code> must be greater than or equal to | ||
| 904 | * <code>minimumFractionDigits</code>. | ||
| 905 | * <p> | ||
| 906 | * <strong>Note:</strong> This field exists only for serialization | ||
| 907 | * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new | ||
| 908 | * <code>int</code> field <code>maximumFractionDigits</code> is used instead. | ||
| 909 | * When writing to a stream, <code>maxFractionDigits</code> is set to | ||
| 910 | * <code>maximumFractionDigits</code> or <code>Byte.MAX_VALUE</code>, | ||
| 911 | * whichever is smaller. When reading from a stream, this field is used | ||
| 912 | * only if <code>serialVersionOnStream</code> is less than 1. | ||
| 913 | * | ||
| 914 | * @serial | ||
| 915 | * @see #getMaximumFractionDigits | ||
| 916 | */ | ||
| 917 | private byte maxFractionDigits = 3; // invariant, >= minFractionDigits | ||
| 918 | |||
| 919 | /** | ||
| 920 | * The minimum number of digits allowed in the fractional portion of a | ||
| 921 | * number. <code>minimumFractionDigits</code> must be less than or equal to | ||
| 922 | * <code>maximumFractionDigits</code>. | ||
| 923 | * <p> | ||
| 924 | * <strong>Note:</strong> This field exists only for serialization | ||
| 925 | * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new | ||
| 926 | * <code>int</code> field <code>minimumFractionDigits</code> is used instead. | ||
| 927 | * When writing to a stream, <code>minFractionDigits</code> is set to | ||
| 928 | * <code>minimumFractionDigits</code> or <code>Byte.MAX_VALUE</code>, | ||
| 929 | * whichever is smaller. When reading from a stream, this field is used | ||
| 930 | * only if <code>serialVersionOnStream</code> is less than 1. | ||
| 931 | * | ||
| 932 | * @serial | ||
| 933 | * @see #getMinimumFractionDigits | ||
| 934 | */ | ||
| 935 | private byte minFractionDigits = 0; | ||
| 936 | |||
| 937 | /** | ||
| 938 | * True if this format will parse numbers as integers only. | ||
| 939 | * | ||
| 940 | * @serial | ||
| 941 | * @see #isParseIntegerOnly | ||
| 942 | */ | ||
| 943 | private boolean parseIntegerOnly = false; | ||
| 944 | |||
| 945 | // new fields for 1.2. byte is too small for integer digits. | ||
| 946 | |||
| 947 | /** | ||
| 948 | * The maximum number of digits allowed in the integer portion of a | ||
| 949 | * number. <code>maximumIntegerDigits</code> must be greater than or equal to | ||
| 950 | * <code>minimumIntegerDigits</code>. | ||
| 951 | * | ||
| 952 | * @serial | ||
| 953 | * @since 1.2 | ||
| 954 | * @see #getMaximumIntegerDigits | ||
| 955 | */ | ||
| 956 | private int maximumIntegerDigits = 40; | ||
| 957 | |||
| 958 | /** | ||
| 959 | * The minimum number of digits allowed in the integer portion of a | ||
| 960 | * number. <code>minimumIntegerDigits</code> must be less than or equal to | ||
| 961 | * <code>maximumIntegerDigits</code>. | ||
| 962 | * | ||
| 963 | * @serial | ||
| 964 | * @since 1.2 | ||
| 965 | * @see #getMinimumIntegerDigits | ||
| 966 | */ | ||
| 967 | private int minimumIntegerDigits = 1; | ||
| 968 | |||
| 969 | /** | ||
| 970 | * The maximum number of digits allowed in the fractional portion of a | ||
| 971 | * number. <code>maximumFractionDigits</code> must be greater than or equal to | ||
| 972 | * <code>minimumFractionDigits</code>. | ||
| 973 | * | ||
| 974 | * @serial | ||
| 975 | * @since 1.2 | ||
| 976 | * @see #getMaximumFractionDigits | ||
| 977 | */ | ||
| 978 | private int maximumFractionDigits = 3; // invariant, >= minFractionDigits | ||
| 979 | |||
| 980 | /** | ||
| 981 | * The minimum number of digits allowed in the fractional portion of a | ||
| 982 | * number. <code>minimumFractionDigits</code> must be less than or equal to | ||
| 983 | * <code>maximumFractionDigits</code>. | ||
| 984 | * | ||
| 985 | * @serial | ||
| 986 | * @since 1.2 | ||
| 987 | * @see #getMinimumFractionDigits | ||
| 988 | */ | ||
| 989 | private int minimumFractionDigits = 0; | ||
| 990 | |||
| 991 | static final int currentSerialVersion = 1; | ||
| 992 | |||
| 993 | /** | ||
| 994 | * Describes the version of <code>NumberFormat</code> present on the stream. | ||
| 995 | * Possible values are: | ||
| 996 | * <ul> | ||
| 997 | * <li><b>0</b> (or uninitialized): the JDK 1.1 version of the stream format. | ||
| 998 | * In this version, the <code>int</code> fields such as | ||
| 999 | * <code>maximumIntegerDigits</code> were not present, and the <code>byte</code> | ||
| 1000 | * fields such as <code>maxIntegerDigits</code> are used instead. | ||
| 1001 | * | ||
| 1002 | * <li><b>1</b>: the 1.2 version of the stream format. The values of the | ||
| 1003 | * <code>byte</code> fields such as <code>maxIntegerDigits</code> are ignored, | ||
| 1004 | * and the <code>int</code> fields such as <code>maximumIntegerDigits</code> | ||
| 1005 | * are used instead. | ||
| 1006 | * </ul> | ||
| 1007 | * When streaming out a <code>NumberFormat</code>, the most recent format | ||
| 1008 | * (corresponding to the highest allowable <code>serialVersionOnStream</code>) | ||
| 1009 | * is always written. | ||
| 1010 | * | ||
| 1011 | * @serial | ||
| 1012 | * @since 1.2 | ||
| 1013 | */ | ||
| 1014 | private int serialVersionOnStream = currentSerialVersion; | ||
| 1015 | |||
| 1016 | // Removed "implements Cloneable" clause. Needs to update serialization | ||
| 1017 | // ID for backward compatibility. | ||
| 1018 | static final long serialVersionUID = -2308460125733713944L; | ||
| 1019 | |||
| 1020 | |||
| 1021 | // | ||
| 1022 | // class for AttributedCharacterIterator attributes | ||
| 1023 | // | ||
| 1024 | /** | ||
| 1025 | * Defines constants that are used as attribute keys in the | ||
| 1026 | * <code>AttributedCharacterIterator</code> returned | ||
| 1027 | * from <code>NumberFormat.formatToCharacterIterator</code> and as | ||
| 1028 | * field identifiers in <code>FieldPosition</code>. | ||
| 1029 | * | ||
| 1030 | * @since 1.4 | ||
| 1031 | */ | ||
| 1032 | public static class Field extends Format.Field { | ||
| 1033 | |||
| 1034 | // Proclaim serial compatibility with 1.4 FCS | ||
| 1035 | private static final long serialVersionUID = 7494728892700160890L; | ||
| 1036 | |||
| 1037 | // table of all instances in this class, used by readResolve | ||
| 1038 | private static final Map instanceMap = new HashMap(11); | ||
| 1039 | |||
| 1040 | /** | ||
| 1041 | * Creates a Field instance with the specified | ||
| 1042 | * name. | ||
| 1043 | * | ||
| 1044 | * @param name Name of the attribute | ||
| 1045 | */ | ||
| 1046 | protected Field(String name) { | ||
| 1047 | super(name); | ||
| 1048 | if (this.getClass() == NumberFormat.Field.class) { | ||
| 1049 | instanceMap.put(name, this); | ||
| 1050 | } | ||
| 1051 | } | ||
| 1052 | |||
| 1053 | /** | ||
| 1054 | * Resolves instances being deserialized to the predefined constants. | ||
| 1055 | * | ||
| 1056 | * @throws InvalidObjectException if the constant could not be resolved. | ||
| 1057 | * @return resolved NumberFormat.Field constant | ||
| 1058 | */ | ||
| 1059 | protected Object readResolve() throws InvalidObjectException { | ||
| 1060 | if (this.getClass() != NumberFormat.Field.class) { | ||
| 1061 | throw new InvalidObjectException("subclass didn't correctly implement readResolve"); | ||
| 1062 | } | ||
| 1063 | |||
| 1064 | Object instance = instanceMap.get(getName()); | ||
| 1065 | if (instance != null) { | ||
| 1066 | return instance; | ||
| 1067 | } else { | ||
| 1068 | throw new InvalidObjectException("unknown attribute name"); | ||
| 1069 | } | ||
| 1070 | } | ||
| 1071 | |||
| 1072 | /** | ||
| 1073 | * Constant identifying the integer field. | ||
| 1074 | */ | ||
| 1075 | public static final Field INTEGER = new Field("integer"); | ||
| 1076 | |||
| 1077 | /** | ||
| 1078 | * Constant identifying the fraction field. | ||
| 1079 | */ | ||
| 1080 | public static final Field FRACTION = new Field("fraction"); | ||
| 1081 | |||
| 1082 | /** | ||
| 1083 | * Constant identifying the exponent field. | ||
| 1084 | */ | ||
| 1085 | public static final Field EXPONENT = new Field("exponent"); | ||
| 1086 | |||
| 1087 | /** | ||
| 1088 | * Constant identifying the decimal separator field. | ||
| 1089 | */ | ||
| 1090 | public static final Field DECIMAL_SEPARATOR = | ||
| 1091 | new Field("decimal separator"); | ||
| 1092 | |||
| 1093 | /** | ||
| 1094 | * Constant identifying the sign field. | ||
| 1095 | */ | ||
| 1096 | public static final Field SIGN = new Field("sign"); | ||
| 1097 | |||
| 1098 | /** | ||
| 1099 | * Constant identifying the grouping separator field. | ||
| 1100 | */ | ||
| 1101 | public static final Field GROUPING_SEPARATOR = | ||
| 1102 | new Field("grouping separator"); | ||
| 1103 | |||
| 1104 | /** | ||
| 1105 | * Constant identifying the exponent symbol field. | ||
| 1106 | */ | ||
| 1107 | public static final Field EXPONENT_SYMBOL = new | ||
| 1108 | Field("exponent symbol"); | ||
| 1109 | |||
| 1110 | /** | ||
| 1111 | * Constant identifying the percent field. | ||
| 1112 | */ | ||
| 1113 | public static final Field PERCENT = new Field("percent"); | ||
| 1114 | |||
| 1115 | /** | ||
| 1116 | * Constant identifying the permille field. | ||
| 1117 | */ | ||
| 1118 | public static final Field PERMILLE = new Field("per mille"); | ||
| 1119 | |||
| 1120 | /** | ||
| 1121 | * Constant identifying the currency field. | ||
| 1122 | */ | ||
| 1123 | public static final Field CURRENCY = new Field("currency"); | ||
| 1124 | |||
| 1125 | /** | ||
| 1126 | * Constant identifying the exponent sign field. | ||
| 1127 | */ | ||
| 1128 | public static final Field EXPONENT_SIGN = new Field("exponent sign"); | ||
| 1129 | } | ||
| 1130 | |||
| 1131 | /** | ||
| 1132 | * Obtains a NumberFormat instance from a NumberFormatProvider implementation. | ||
| 1133 | */ | ||
| 1134 | private static class NumberFormatGetter | ||
| 1135 | implements LocaleServiceProviderPool.LocalizedObjectGetter<NumberFormatProvider, | ||
| 1136 | NumberFormat> { | ||
| 1137 | private static final NumberFormatGetter INSTANCE = new NumberFormatGetter(); | ||
| 1138 | |||
| 1139 | public NumberFormat getObject(NumberFormatProvider numberFormatProvider, | ||
| 1140 | Locale locale, | ||
| 1141 | String key, | ||
| 1142 | Object... params) { | ||
| 1143 | assert params.length == 1; | ||
| 1144 | int choice = (Integer)params[0]; | ||
| 1145 | |||
| 1146 | switch (choice) { | ||
| 1147 | case NUMBERSTYLE: | ||
| 1148 | return numberFormatProvider.getNumberInstance(locale); | ||
| 1149 | case PERCENTSTYLE: | ||
| 1150 | return numberFormatProvider.getPercentInstance(locale); | ||
| 1151 | case CURRENCYSTYLE: | ||
| 1152 | return numberFormatProvider.getCurrencyInstance(locale); | ||
| 1153 | case INTEGERSTYLE: | ||
| 1154 | return numberFormatProvider.getIntegerInstance(locale); | ||
| 1155 | default: | ||
| 1156 | assert false : choice; | ||
| 1157 | } | ||
| 1158 | |||
| 1159 | return null; | ||
| 1160 | } | ||
| 1161 | } | ||
| 1162 | } | ||
Binary file not shown.
| @@ -0,0 +1,78 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 1998, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Signals that an error has been reached unexpectedly | ||
| 43 | * while parsing. | ||
| 44 | * @see java.lang.Exception | ||
| 45 | * @see java.text.Format | ||
| 46 | * @see java.text.FieldPosition | ||
| 47 | * @author Mark Davis | ||
| 48 | */ | ||
| 49 | public | ||
| 50 | class ParseException extends Exception { | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Constructs a ParseException with the specified detail message and | ||
| 54 | * offset. | ||
| 55 | * A detail message is a String that describes this particular exception. | ||
| 56 | * @param s the detail message | ||
| 57 | * @param errorOffset the position where the error is found while parsing. | ||
| 58 | */ | ||
| 59 | public ParseException(String s, int errorOffset) { | ||
| 60 | super(s); | ||
| 61 | this.errorOffset = errorOffset; | ||
| 62 | } | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Returns the position where the error was found. | ||
| 66 | */ | ||
| 67 | public int getErrorOffset () { | ||
| 68 | return errorOffset; | ||
| 69 | } | ||
| 70 | |||
| 71 | //============ privates ============ | ||
| 72 | /** | ||
| 73 | * The zero-based character offset into the string being parsed at which | ||
| 74 | * the error was found during parsing. | ||
| 75 | * @serial | ||
| 76 | */ | ||
| 77 | private int errorOffset; | ||
| 78 | } | ||
Binary file not shown.
| @@ -0,0 +1,139 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2002, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | |||
| 42 | /** | ||
| 43 | * <code>ParsePosition</code> is a simple class used by <code>Format</code> | ||
| 44 | * and its subclasses to keep track of the current position during parsing. | ||
| 45 | * The <code>parseObject</code> method in the various <code>Format</code> | ||
| 46 | * classes requires a <code>ParsePosition</code> object as an argument. | ||
| 47 | * | ||
| 48 | * <p> | ||
| 49 | * By design, as you parse through a string with different formats, | ||
| 50 | * you can use the same <code>ParsePosition</code>, since the index parameter | ||
| 51 | * records the current position. | ||
| 52 | * | ||
| 53 | * @author Mark Davis | ||
| 54 | * @see java.text.Format | ||
| 55 | */ | ||
| 56 | |||
| 57 | public class ParsePosition { | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Input: the place you start parsing. | ||
| 61 | * <br>Output: position where the parse stopped. | ||
| 62 | * This is designed to be used serially, | ||
| 63 | * with each call setting index up for the next one. | ||
| 64 | */ | ||
| 65 | int index = 0; | ||
| 66 | int errorIndex = -1; | ||
| 67 | |||
| 68 | /** | ||
| 69 | * Retrieve the current parse position. On input to a parse method, this | ||
| 70 | * is the index of the character at which parsing will begin; on output, it | ||
| 71 | * is the index of the character following the last character parsed. | ||
| 72 | */ | ||
| 73 | public int getIndex() { | ||
| 74 | return index; | ||
| 75 | } | ||
| 76 | |||
| 77 | /** | ||
| 78 | * Set the current parse position. | ||
| 79 | */ | ||
| 80 | public void setIndex(int index) { | ||
| 81 | this.index = index; | ||
| 82 | } | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Create a new ParsePosition with the given initial index. | ||
| 86 | */ | ||
| 87 | public ParsePosition(int index) { | ||
| 88 | this.index = index; | ||
| 89 | } | ||
| 90 | /** | ||
| 91 | * Set the index at which a parse error occurred. Formatters | ||
| 92 | * should set this before returning an error code from their | ||
| 93 | * parseObject method. The default value is -1 if this is not set. | ||
| 94 | * @since 1.2 | ||
| 95 | */ | ||
| 96 | public void setErrorIndex(int ei) | ||
| 97 | { | ||
| 98 | errorIndex = ei; | ||
| 99 | } | ||
| 100 | |||
| 101 | /** | ||
| 102 | * Retrieve the index at which an error occurred, or -1 if the | ||
| 103 | * error index has not been set. | ||
| 104 | * @since 1.2 | ||
| 105 | */ | ||
| 106 | public int getErrorIndex() | ||
| 107 | { | ||
| 108 | return errorIndex; | ||
| 109 | } | ||
| 110 | /** | ||
| 111 | * Overrides equals | ||
| 112 | */ | ||
| 113 | public boolean equals(Object obj) | ||
| 114 | { | ||
| 115 | if (obj == null) return false; | ||
| 116 | if (!(obj instanceof ParsePosition)) | ||
| 117 | return false; | ||
| 118 | ParsePosition other = (ParsePosition) obj; | ||
| 119 | return (index == other.index && errorIndex == other.errorIndex); | ||
| 120 | } | ||
| 121 | |||
| 122 | /** | ||
| 123 | * Returns a hash code for this ParsePosition. | ||
| 124 | * @return a hash code value for this object | ||
| 125 | */ | ||
| 126 | public int hashCode() { | ||
| 127 | return (errorIndex << 16) | index; | ||
| 128 | } | ||
| 129 | |||
| 130 | /** | ||
| 131 | * Return a string representation of this ParsePosition. | ||
| 132 | * @return a string representation of this object | ||
| 133 | */ | ||
| 134 | public String toString() { | ||
| 135 | return getClass().getName() + | ||
| 136 | "[index=" + index + | ||
| 137 | ",errorIndex=" + errorIndex + ']'; | ||
| 138 | } | ||
| 139 | } | ||
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,306 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1996, 2000, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996, 1997 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.lang.Character; | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Utility class for normalizing and merging patterns for collation. | ||
| 45 | * This is to be used with MergeCollation for adding patterns to an | ||
| 46 | * existing rule table. | ||
| 47 | * @see MergeCollation | ||
| 48 | * @author Mark Davis, Helena Shih | ||
| 49 | */ | ||
| 50 | |||
| 51 | class PatternEntry { | ||
| 52 | /** | ||
| 53 | * Gets the current extension, quoted | ||
| 54 | */ | ||
| 55 | public void appendQuotedExtension(StringBuffer toAddTo) { | ||
| 56 | appendQuoted(extension,toAddTo); | ||
| 57 | } | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Gets the current chars, quoted | ||
| 61 | */ | ||
| 62 | public void appendQuotedChars(StringBuffer toAddTo) { | ||
| 63 | appendQuoted(chars,toAddTo); | ||
| 64 | } | ||
| 65 | |||
| 66 | /** | ||
| 67 | * WARNING this is used for searching in a Vector. | ||
| 68 | * Because Vector.indexOf doesn't take a comparator, | ||
| 69 | * this method is ill-defined and ignores strength. | ||
| 70 | */ | ||
| 71 | public boolean equals(Object obj) { | ||
| 72 | if (obj == null) return false; | ||
| 73 | PatternEntry other = (PatternEntry) obj; | ||
| 74 | boolean result = chars.equals(other.chars); | ||
| 75 | return result; | ||
| 76 | } | ||
| 77 | |||
| 78 | public int hashCode() { | ||
| 79 | return chars.hashCode(); | ||
| 80 | } | ||
| 81 | |||
| 82 | /** | ||
| 83 | * For debugging. | ||
| 84 | */ | ||
| 85 | public String toString() { | ||
| 86 | StringBuffer result = new StringBuffer(); | ||
| 87 | addToBuffer(result, true, false, null); | ||
| 88 | return result.toString(); | ||
| 89 | } | ||
| 90 | |||
| 91 | /** | ||
| 92 | * Gets the strength of the entry. | ||
| 93 | */ | ||
| 94 | final int getStrength() { | ||
| 95 | return strength; | ||
| 96 | } | ||
| 97 | |||
| 98 | /** | ||
| 99 | * Gets the expanding characters of the entry. | ||
| 100 | */ | ||
| 101 | final String getExtension() { | ||
| 102 | return extension; | ||
| 103 | } | ||
| 104 | |||
| 105 | /** | ||
| 106 | * Gets the core characters of the entry. | ||
| 107 | */ | ||
| 108 | final String getChars() { | ||
| 109 | return chars; | ||
| 110 | } | ||
| 111 | |||
| 112 | // ===== privates ===== | ||
| 113 | |||
| 114 | void addToBuffer(StringBuffer toAddTo, | ||
| 115 | boolean showExtension, | ||
| 116 | boolean showWhiteSpace, | ||
| 117 | PatternEntry lastEntry) | ||
| 118 | { | ||
| 119 | if (showWhiteSpace && toAddTo.length() > 0) | ||
| 120 | if (strength == Collator.PRIMARY || lastEntry != null) | ||
| 121 | toAddTo.append('\n'); | ||
| 122 | else | ||
| 123 | toAddTo.append(' '); | ||
| 124 | if (lastEntry != null) { | ||
| 125 | toAddTo.append('&'); | ||
| 126 | if (showWhiteSpace) | ||
| 127 | toAddTo.append(' '); | ||
| 128 | lastEntry.appendQuotedChars(toAddTo); | ||
| 129 | appendQuotedExtension(toAddTo); | ||
| 130 | if (showWhiteSpace) | ||
| 131 | toAddTo.append(' '); | ||
| 132 | } | ||
| 133 | switch (strength) { | ||
| 134 | case Collator.IDENTICAL: toAddTo.append('='); break; | ||
| 135 | case Collator.TERTIARY: toAddTo.append(','); break; | ||
| 136 | case Collator.SECONDARY: toAddTo.append(';'); break; | ||
| 137 | case Collator.PRIMARY: toAddTo.append('<'); break; | ||
| 138 | case RESET: toAddTo.append('&'); break; | ||
| 139 | case UNSET: toAddTo.append('?'); break; | ||
| 140 | } | ||
| 141 | if (showWhiteSpace) | ||
| 142 | toAddTo.append(' '); | ||
| 143 | appendQuoted(chars,toAddTo); | ||
| 144 | if (showExtension && extension.length() != 0) { | ||
| 145 | toAddTo.append('/'); | ||
| 146 | appendQuoted(extension,toAddTo); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | static void appendQuoted(String chars, StringBuffer toAddTo) { | ||
| 151 | boolean inQuote = false; | ||
| 152 | char ch = chars.charAt(0); | ||
| 153 | if (Character.isSpaceChar(ch)) { | ||
| 154 | inQuote = true; | ||
| 155 | toAddTo.append('\''); | ||
| 156 | } else { | ||
| 157 | if (PatternEntry.isSpecialChar(ch)) { | ||
| 158 | inQuote = true; | ||
| 159 | toAddTo.append('\''); | ||
| 160 | } else { | ||
| 161 | switch (ch) { | ||
| 162 | case 0x0010: case '\f': case '\r': | ||
| 163 | case '\t': case '\n': case '@': | ||
| 164 | inQuote = true; | ||
| 165 | toAddTo.append('\''); | ||
| 166 | break; | ||
| 167 | case '\'': | ||
| 168 | inQuote = true; | ||
| 169 | toAddTo.append('\''); | ||
| 170 | break; | ||
| 171 | default: | ||
| 172 | if (inQuote) { | ||
| 173 | inQuote = false; toAddTo.append('\''); | ||
| 174 | } | ||
| 175 | break; | ||
| 176 | } | ||
| 177 | } | ||
| 178 | } | ||
| 179 | toAddTo.append(chars); | ||
| 180 | if (inQuote) | ||
| 181 | toAddTo.append('\''); | ||
| 182 | } | ||
| 183 | |||
| 184 | //======================================================================== | ||
| 185 | // Parsing a pattern into a list of PatternEntries.... | ||
| 186 | //======================================================================== | ||
| 187 | |||
| 188 | PatternEntry(int strength, | ||
| 189 | StringBuffer chars, | ||
| 190 | StringBuffer extension) | ||
| 191 | { | ||
| 192 | this.strength = strength; | ||
| 193 | this.chars = chars.toString(); | ||
| 194 | this.extension = (extension.length() > 0) ? extension.toString() | ||
| 195 | : ""; | ||
| 196 | } | ||
| 197 | |||
| 198 | static class Parser { | ||
| 199 | private String pattern; | ||
| 200 | private int i; | ||
| 201 | |||
| 202 | public Parser(String pattern) { | ||
| 203 | this.pattern = pattern; | ||
| 204 | this.i = 0; | ||
| 205 | } | ||
| 206 | |||
| 207 | public PatternEntry next() throws ParseException { | ||
| 208 | int newStrength = UNSET; | ||
| 209 | |||
| 210 | newChars.setLength(0); | ||
| 211 | newExtension.setLength(0); | ||
| 212 | |||
| 213 | boolean inChars = true; | ||
| 214 | boolean inQuote = false; | ||
| 215 | mainLoop: | ||
| 216 | while (i < pattern.length()) { | ||
| 217 | char ch = pattern.charAt(i); | ||
| 218 | if (inQuote) { | ||
| 219 | if (ch == '\'') { | ||
| 220 | inQuote = false; | ||
| 221 | } else { | ||
| 222 | if (newChars.length() == 0) newChars.append(ch); | ||
| 223 | else if (inChars) newChars.append(ch); | ||
| 224 | else newExtension.append(ch); | ||
| 225 | } | ||
| 226 | } else switch (ch) { | ||
| 227 | case '=': if (newStrength != UNSET) break mainLoop; | ||
| 228 | newStrength = Collator.IDENTICAL; break; | ||
| 229 | case ',': if (newStrength != UNSET) break mainLoop; | ||
| 230 | newStrength = Collator.TERTIARY; break; | ||
| 231 | case ';': if (newStrength != UNSET) break mainLoop; | ||
| 232 | newStrength = Collator.SECONDARY; break; | ||
| 233 | case '<': if (newStrength != UNSET) break mainLoop; | ||
| 234 | newStrength = Collator.PRIMARY; break; | ||
| 235 | case '&': if (newStrength != UNSET) break mainLoop; | ||
| 236 | newStrength = RESET; break; | ||
| 237 | case '\t': | ||
| 238 | case '\n': | ||
| 239 | case '\f': | ||
| 240 | case '\r': | ||
| 241 | case ' ': break; // skip whitespace TODO use Character | ||
| 242 | case '/': inChars = false; break; | ||
| 243 | case '\'': | ||
| 244 | inQuote = true; | ||
| 245 | ch = pattern.charAt(++i); | ||
| 246 | if (newChars.length() == 0) newChars.append(ch); | ||
| 247 | else if (inChars) newChars.append(ch); | ||
| 248 | else newExtension.append(ch); | ||
| 249 | break; | ||
| 250 | default: | ||
| 251 | if (newStrength == UNSET) { | ||
| 252 | throw new ParseException | ||
| 253 | ("missing char (=,;<&) : " + | ||
| 254 | pattern.substring(i, | ||
| 255 | (i+10 < pattern.length()) ? | ||
| 256 | i+10 : pattern.length()), | ||
| 257 | i); | ||
| 258 | } | ||
| 259 | if (PatternEntry.isSpecialChar(ch) && (inQuote == false)) | ||
| 260 | throw new ParseException | ||
| 261 | ("Unquoted punctuation character : " + Integer.toString(ch, 16), i); | ||
| 262 | if (inChars) { | ||
| 263 | newChars.append(ch); | ||
| 264 | } else { | ||
| 265 | newExtension.append(ch); | ||
| 266 | } | ||
| 267 | break; | ||
| 268 | } | ||
| 269 | i++; | ||
| 270 | } | ||
| 271 | if (newStrength == UNSET) | ||
| 272 | return null; | ||
| 273 | if (newChars.length() == 0) { | ||
| 274 | throw new ParseException | ||
| 275 | ("missing chars (=,;<&): " + | ||
| 276 | pattern.substring(i, | ||
| 277 | (i+10 < pattern.length()) ? | ||
| 278 | i+10 : pattern.length()), | ||
| 279 | i); | ||
| 280 | } | ||
| 281 | |||
| 282 | return new PatternEntry(newStrength, newChars, newExtension); | ||
| 283 | } | ||
| 284 | |||
| 285 | // We re-use these objects in order to improve performance | ||
| 286 | private StringBuffer newChars = new StringBuffer(); | ||
| 287 | private StringBuffer newExtension = new StringBuffer(); | ||
| 288 | |||
| 289 | } | ||
| 290 | |||
| 291 | static boolean isSpecialChar(char ch) { | ||
| 292 | return ((ch == '\u0020') || | ||
| 293 | ((ch <= '\u002F') && (ch >= '\u0022')) || | ||
| 294 | ((ch <= '\u003F') && (ch >= '\u003A')) || | ||
| 295 | ((ch <= '\u0060') && (ch >= '\u005B')) || | ||
| 296 | ((ch <= '\u007E') && (ch >= '\u007B'))); | ||
| 297 | } | ||
| 298 | |||
| 299 | |||
| 300 | static final int RESET = -2; | ||
| 301 | static final int UNSET = -1; | ||
| 302 | |||
| 303 | int strength = UNSET; | ||
| 304 | String chars = ""; | ||
| 305 | String extension = ""; | ||
| 306 | } | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,303 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1999, 2003, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.util.Vector; | ||
| 42 | import sun.text.UCompactIntArray; | ||
| 43 | import sun.text.IntHashtable; | ||
| 44 | |||
| 45 | /** | ||
| 46 | * This class contains the static state of a RuleBasedCollator: The various | ||
| 47 | * tables that are used by the collation routines. Several RuleBasedCollators | ||
| 48 | * can share a single RBCollationTables object, easing memory requirements and | ||
| 49 | * improving performance. | ||
| 50 | */ | ||
| 51 | final class RBCollationTables { | ||
| 52 | //=========================================================================================== | ||
| 53 | // The following diagram shows the data structure of the RBCollationTables object. | ||
| 54 | // Suppose we have the rule, where 'o-umlaut' is the unicode char 0x00F6. | ||
| 55 | // "a, A < b, B < c, C, ch, cH, Ch, CH < d, D ... < o, O; 'o-umlaut'/E, 'O-umlaut'/E ...". | ||
| 56 | // What the rule says is, sorts 'ch'ligatures and 'c' only with tertiary difference and | ||
| 57 | // sorts 'o-umlaut' as if it's always expanded with 'e'. | ||
| 58 | // | ||
| 59 | // mapping table contracting list expanding list | ||
| 60 | // (contains all unicode char | ||
| 61 | // entries) ___ ____________ _________________________ | ||
| 62 | // ________ +>|_*_|->|'c' |v('c') | +>|v('o')|v('umlaut')|v('e')| | ||
| 63 | // |_\u0001_|-> v('\u0001') | |_:_| |------------| | |-------------------------| | ||
| 64 | // |_\u0002_|-> v('\u0002') | |_:_| |'ch'|v('ch')| | | : | | ||
| 65 | // |____:___| | |_:_| |------------| | |-------------------------| | ||
| 66 | // |____:___| | |'cH'|v('cH')| | | : | | ||
| 67 | // |__'a'___|-> v('a') | |------------| | |-------------------------| | ||
| 68 | // |__'b'___|-> v('b') | |'Ch'|v('Ch')| | | : | | ||
| 69 | // |____:___| | |------------| | |-------------------------| | ||
| 70 | // |____:___| | |'CH'|v('CH')| | | : | | ||
| 71 | // |___'c'__|---------------- ------------ | |-------------------------| | ||
| 72 | // |____:___| | | : | | ||
| 73 | // |o-umlaut|---------------------------------------- |_________________________| | ||
| 74 | // |____:___| | ||
| 75 | // | ||
| 76 | // Noted by Helena Shih on 6/23/97 | ||
| 77 | //============================================================================================ | ||
| 78 | |||
| 79 | public RBCollationTables(String rules, int decmp) throws ParseException { | ||
| 80 | this.rules = rules; | ||
| 81 | |||
| 82 | RBTableBuilder builder = new RBTableBuilder(new BuildAPI()); | ||
| 83 | builder.build(rules, decmp); // this object is filled in through | ||
| 84 | // the BuildAPI object | ||
| 85 | } | ||
| 86 | |||
| 87 | final class BuildAPI { | ||
| 88 | /** | ||
| 89 | * Private constructor. Prevents anyone else besides RBTableBuilder | ||
| 90 | * from gaining direct access to the internals of this class. | ||
| 91 | */ | ||
| 92 | private BuildAPI() { | ||
| 93 | } | ||
| 94 | |||
| 95 | /** | ||
| 96 | * This function is used by RBTableBuilder to fill in all the members of this | ||
| 97 | * object. (Effectively, the builder class functions as a "friend" of this | ||
| 98 | * class, but to avoid changing too much of the logic, it carries around "shadow" | ||
| 99 | * copies of all these variables until the end of the build process and then | ||
| 100 | * copies them en masse into the actual tables object once all the construction | ||
| 101 | * logic is complete. This function does that "copying en masse". | ||
| 102 | * @param f2ary The value for frenchSec (the French-secondary flag) | ||
| 103 | * @param swap The value for SE Asian swapping rule | ||
| 104 | * @param map The collator's character-mapping table (the value for mapping) | ||
| 105 | * @param cTbl The collator's contracting-character table (the value for contractTable) | ||
| 106 | * @param eTbl The collator's expanding-character table (the value for expandTable) | ||
| 107 | * @param cFlgs The hash table of characters that participate in contracting- | ||
| 108 | * character sequences (the value for contractFlags) | ||
| 109 | * @param mso The value for maxSecOrder | ||
| 110 | * @param mto The value for maxTerOrder | ||
| 111 | */ | ||
| 112 | void fillInTables(boolean f2ary, | ||
| 113 | boolean swap, | ||
| 114 | UCompactIntArray map, | ||
| 115 | Vector cTbl, | ||
| 116 | Vector eTbl, | ||
| 117 | IntHashtable cFlgs, | ||
| 118 | short mso, | ||
| 119 | short mto) { | ||
| 120 | frenchSec = f2ary; | ||
| 121 | seAsianSwapping = swap; | ||
| 122 | mapping = map; | ||
| 123 | contractTable = cTbl; | ||
| 124 | expandTable = eTbl; | ||
| 125 | contractFlags = cFlgs; | ||
| 126 | maxSecOrder = mso; | ||
| 127 | maxTerOrder = mto; | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | /** | ||
| 132 | * Gets the table-based rules for the collation object. | ||
| 133 | * @return returns the collation rules that the table collation object | ||
| 134 | * was created from. | ||
| 135 | */ | ||
| 136 | public String getRules() | ||
| 137 | { | ||
| 138 | return rules; | ||
| 139 | } | ||
| 140 | |||
| 141 | public boolean isFrenchSec() { | ||
| 142 | return frenchSec; | ||
| 143 | } | ||
| 144 | |||
| 145 | public boolean isSEAsianSwapping() { | ||
| 146 | return seAsianSwapping; | ||
| 147 | } | ||
| 148 | |||
| 149 | // ============================================================== | ||
| 150 | // internal (for use by CollationElementIterator) | ||
| 151 | // ============================================================== | ||
| 152 | |||
| 153 | /** | ||
| 154 | * Get the entry of hash table of the contracting string in the collation | ||
| 155 | * table. | ||
| 156 | * @param ch the starting character of the contracting string | ||
| 157 | */ | ||
| 158 | Vector getContractValues(int ch) | ||
| 159 | { | ||
| 160 | int index = mapping.elementAt(ch); | ||
| 161 | return getContractValuesImpl(index - CONTRACTCHARINDEX); | ||
| 162 | } | ||
| 163 | |||
| 164 | //get contract values from contractTable by index | ||
| 165 | private Vector getContractValuesImpl(int index) | ||
| 166 | { | ||
| 167 | if (index >= 0) | ||
| 168 | { | ||
| 169 | return (Vector)contractTable.elementAt(index); | ||
| 170 | } | ||
| 171 | else // not found | ||
| 172 | { | ||
| 173 | return null; | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | /** | ||
| 178 | * Returns true if this character appears anywhere in a contracting | ||
| 179 | * character sequence. (Used by CollationElementIterator.setOffset().) | ||
| 180 | */ | ||
| 181 | boolean usedInContractSeq(int c) { | ||
| 182 | return contractFlags.get(c) == 1; | ||
| 183 | } | ||
| 184 | |||
| 185 | /** | ||
| 186 | * Return the maximum length of any expansion sequences that end | ||
| 187 | * with the specified comparison order. | ||
| 188 | * | ||
| 189 | * @param order a collation order returned by previous or next. | ||
| 190 | * @return the maximum length of any expansion seuences ending | ||
| 191 | * with the specified order. | ||
| 192 | * | ||
| 193 | * @see CollationElementIterator#getMaxExpansion | ||
| 194 | */ | ||
| 195 | int getMaxExpansion(int order) | ||
| 196 | { | ||
| 197 | int result = 1; | ||
| 198 | |||
| 199 | if (expandTable != null) { | ||
| 200 | // Right now this does a linear search through the entire | ||
| 201 | // expandsion table. If a collator had a large number of expansions, | ||
| 202 | // this could cause a performance problem, but in practise that | ||
| 203 | // rarely happens | ||
| 204 | for (int i = 0; i < expandTable.size(); i++) { | ||
| 205 | int[] valueList = (int [])expandTable.elementAt(i); | ||
| 206 | int length = valueList.length; | ||
| 207 | |||
| 208 | if (length > result && valueList[length-1] == order) { | ||
| 209 | result = length; | ||
| 210 | } | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | return result; | ||
| 215 | } | ||
| 216 | |||
| 217 | /** | ||
| 218 | * Get the entry of hash table of the expanding string in the collation | ||
| 219 | * table. | ||
| 220 | * @param idx the index of the expanding string value list | ||
| 221 | */ | ||
| 222 | final int[] getExpandValueList(int order) { | ||
| 223 | return (int[])expandTable.elementAt(order - EXPANDCHARINDEX); | ||
| 224 | } | ||
| 225 | |||
| 226 | /** | ||
| 227 | * Get the comarison order of a character from the collation table. | ||
| 228 | * @return the comparison order of a character. | ||
| 229 | */ | ||
| 230 | int getUnicodeOrder(int ch) | ||
| 231 | { | ||
| 232 | return mapping.elementAt(ch); | ||
| 233 | } | ||
| 234 | |||
| 235 | short getMaxSecOrder() { | ||
| 236 | return maxSecOrder; | ||
| 237 | } | ||
| 238 | |||
| 239 | short getMaxTerOrder() { | ||
| 240 | return maxTerOrder; | ||
| 241 | } | ||
| 242 | |||
| 243 | /** | ||
| 244 | * Reverse a string. | ||
| 245 | */ | ||
| 246 | //shemran/Note: this is used for secondary order value reverse, no | ||
| 247 | // need to consider supplementary pair. | ||
| 248 | static void reverse (StringBuffer result, int from, int to) | ||
| 249 | { | ||
| 250 | int i = from; | ||
| 251 | char swap; | ||
| 252 | |||
| 253 | int j = to - 1; | ||
| 254 | while (i < j) { | ||
| 255 | swap = result.charAt(i); | ||
| 256 | result.setCharAt(i, result.charAt(j)); | ||
| 257 | result.setCharAt(j, swap); | ||
| 258 | i++; | ||
| 259 | j--; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | final static int getEntry(Vector list, String name, boolean fwd) { | ||
| 264 | for (int i = 0; i < list.size(); i++) { | ||
| 265 | EntryPair pair = (EntryPair)list.elementAt(i); | ||
| 266 | if (pair.fwd == fwd && pair.entryName.equals(name)) { | ||
| 267 | return i; | ||
| 268 | } | ||
| 269 | } | ||
| 270 | return UNMAPPED; | ||
| 271 | } | ||
| 272 | |||
| 273 | // ============================================================== | ||
| 274 | // constants | ||
| 275 | // ============================================================== | ||
| 276 | //sherman/Todo: is the value big enough????? | ||
| 277 | final static int EXPANDCHARINDEX = 0x7E000000; // Expand index follows | ||
| 278 | final static int CONTRACTCHARINDEX = 0x7F000000; // contract indexes follow | ||
| 279 | final static int UNMAPPED = 0xFFFFFFFF; | ||
| 280 | |||
| 281 | final static int PRIMARYORDERMASK = 0xffff0000; | ||
| 282 | final static int SECONDARYORDERMASK = 0x0000ff00; | ||
| 283 | final static int TERTIARYORDERMASK = 0x000000ff; | ||
| 284 | final static int PRIMARYDIFFERENCEONLY = 0xffff0000; | ||
| 285 | final static int SECONDARYDIFFERENCEONLY = 0xffffff00; | ||
| 286 | final static int PRIMARYORDERSHIFT = 16; | ||
| 287 | final static int SECONDARYORDERSHIFT = 8; | ||
| 288 | |||
| 289 | // ============================================================== | ||
| 290 | // instance variables | ||
| 291 | // ============================================================== | ||
| 292 | private String rules = null; | ||
| 293 | private boolean frenchSec = false; | ||
| 294 | private boolean seAsianSwapping = false; | ||
| 295 | |||
| 296 | private UCompactIntArray mapping = null; | ||
| 297 | private Vector contractTable = null; | ||
| 298 | private Vector expandTable = null; | ||
| 299 | private IntHashtable contractFlags = null; | ||
| 300 | |||
| 301 | private short maxSecOrder = 0; | ||
| 302 | private short maxTerOrder = 0; | ||
| 303 | } | ||
Binary file not shown.
| @@ -0,0 +1,618 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1999, 2005, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.util.Vector; | ||
| 42 | import sun.text.UCompactIntArray; | ||
| 43 | import sun.text.IntHashtable; | ||
| 44 | import sun.text.ComposedCharIter; | ||
| 45 | import sun.text.CollatorUtilities; | ||
| 46 | import sun.text.normalizer.NormalizerImpl; | ||
| 47 | |||
| 48 | /** | ||
| 49 | * This class contains all the code to parse a RuleBasedCollator pattern | ||
| 50 | * and build a RBCollationTables object from it. A particular instance | ||
| 51 | * of tis class exists only during the actual build process-- once an | ||
| 52 | * RBCollationTables object has been built, the RBTableBuilder object | ||
| 53 | * goes away. This object carries all of the state which is only needed | ||
| 54 | * during the build process, plus a "shadow" copy of all of the state | ||
| 55 | * that will go into the tables object itself. This object communicates | ||
| 56 | * with RBCollationTables through a separate class, RBCollationTables.BuildAPI, | ||
| 57 | * this is an inner class of RBCollationTables and provides a separate | ||
| 58 | * private API for communication with RBTableBuilder. | ||
| 59 | * This class isn't just an inner class of RBCollationTables itself because | ||
| 60 | * of its large size. For source-code readability, it seemed better for the | ||
| 61 | * builder to have its own source file. | ||
| 62 | */ | ||
| 63 | final class RBTableBuilder { | ||
| 64 | |||
| 65 | public RBTableBuilder(RBCollationTables.BuildAPI tables) { | ||
| 66 | this.tables = tables; | ||
| 67 | } | ||
| 68 | |||
| 69 | /** | ||
| 70 | * Create a table-based collation object with the given rules. | ||
| 71 | * This is the main function that actually builds the tables and | ||
| 72 | * stores them back in the RBCollationTables object. It is called | ||
| 73 | * ONLY by the RBCollationTables constructor. | ||
| 74 | * @see java.util.RuleBasedCollator#RuleBasedCollator | ||
| 75 | * @exception ParseException If the rules format is incorrect. | ||
| 76 | */ | ||
| 77 | |||
| 78 | public void build(String pattern, int decmp) throws ParseException | ||
| 79 | { | ||
| 80 | boolean isSource = true; | ||
| 81 | int i = 0; | ||
| 82 | String expChars; | ||
| 83 | String groupChars; | ||
| 84 | if (pattern.length() == 0) | ||
| 85 | throw new ParseException("Build rules empty.", 0); | ||
| 86 | |||
| 87 | // This array maps Unicode characters to their collation ordering | ||
| 88 | mapping = new UCompactIntArray((int)RBCollationTables.UNMAPPED); | ||
| 89 | // Normalize the build rules. Find occurances of all decomposed characters | ||
| 90 | // and normalize the rules before feeding into the builder. By "normalize", | ||
| 91 | // we mean that all precomposed Unicode characters must be converted into | ||
| 92 | // a base character and one or more combining characters (such as accents). | ||
| 93 | // When there are multiple combining characters attached to a base character, | ||
| 94 | // the combining characters must be in their canonical order | ||
| 95 | // | ||
| 96 | // sherman/Note: | ||
| 97 | //(1)decmp will be NO_DECOMPOSITION only in ko locale to prevent decompose | ||
| 98 | //hangual syllables to jamos, so we can actually just call decompose with | ||
| 99 | //normalizer's IGNORE_HANGUL option turned on | ||
| 100 | // | ||
| 101 | //(2)just call the "special version" in NormalizerImpl directly | ||
| 102 | //pattern = Normalizer.decompose(pattern, false, Normalizer.IGNORE_HANGUL, true); | ||
| 103 | // | ||
| 104 | //Normalizer.Mode mode = CollatorUtilities.toNormalizerMode(decmp); | ||
| 105 | //pattern = Normalizer.normalize(pattern, mode, 0, true); | ||
| 106 | |||
| 107 | pattern = NormalizerImpl.canonicalDecomposeWithSingleQuotation(pattern); | ||
| 108 | |||
| 109 | // Build the merged collation entries | ||
| 110 | // Since rules can be specified in any order in the string | ||
| 111 | // (e.g. "c , C < d , D < e , E .... C < CH") | ||
| 112 | // this splits all of the rules in the string out into separate | ||
| 113 | // objects and then sorts them. In the above example, it merges the | ||
| 114 | // "C < CH" rule in just before the "C < D" rule. | ||
| 115 | // | ||
| 116 | |||
| 117 | mPattern = new MergeCollation(pattern); | ||
| 118 | |||
| 119 | int order = 0; | ||
| 120 | |||
| 121 | // Now walk though each entry and add it to my own tables | ||
| 122 | for (i = 0; i < mPattern.getCount(); ++i) | ||
| 123 | { | ||
| 124 | PatternEntry entry = mPattern.getItemAt(i); | ||
| 125 | if (entry != null) { | ||
| 126 | groupChars = entry.getChars(); | ||
| 127 | if (groupChars.length() > 1) { | ||
| 128 | switch(groupChars.charAt(groupChars.length()-1)) { | ||
| 129 | case '@': | ||
| 130 | frenchSec = true; | ||
| 131 | groupChars = groupChars.substring(0, groupChars.length()-1); | ||
| 132 | break; | ||
| 133 | case '!': | ||
| 134 | seAsianSwapping = true; | ||
| 135 | groupChars = groupChars.substring(0, groupChars.length()-1); | ||
| 136 | break; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | order = increment(entry.getStrength(), order); | ||
| 141 | expChars = entry.getExtension(); | ||
| 142 | |||
| 143 | if (expChars.length() != 0) { | ||
| 144 | addExpandOrder(groupChars, expChars, order); | ||
| 145 | } else if (groupChars.length() > 1) { | ||
| 146 | char ch = groupChars.charAt(0); | ||
| 147 | if (Character.isHighSurrogate(ch) && groupChars.length() == 2) { | ||
| 148 | addOrder(Character.toCodePoint(ch, groupChars.charAt(1)), order); | ||
| 149 | } else { | ||
| 150 | addContractOrder(groupChars, order); | ||
| 151 | } | ||
| 152 | } else { | ||
| 153 | char ch = groupChars.charAt(0); | ||
| 154 | addOrder(ch, order); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | addComposedChars(); | ||
| 159 | |||
| 160 | commit(); | ||
| 161 | mapping.compact(); | ||
| 162 | /* | ||
| 163 | System.out.println("mappingSize=" + mapping.getKSize()); | ||
| 164 | for (int j = 0; j < 0xffff; j++) { | ||
| 165 | int value = mapping.elementAt(j); | ||
| 166 | if (value != RBCollationTables.UNMAPPED) | ||
| 167 | System.out.println("index=" + Integer.toString(j, 16) | ||
| 168 | + ", value=" + Integer.toString(value, 16)); | ||
| 169 | } | ||
| 170 | */ | ||
| 171 | tables.fillInTables(frenchSec, seAsianSwapping, mapping, contractTable, expandTable, | ||
| 172 | contractFlags, maxSecOrder, maxTerOrder); | ||
| 173 | } | ||
| 174 | |||
| 175 | /** Add expanding entries for pre-composed unicode characters so that this | ||
| 176 | * collator can be used reasonably well with decomposition turned off. | ||
| 177 | */ | ||
| 178 | private void addComposedChars() throws ParseException { | ||
| 179 | // Iterate through all of the pre-composed characters in Unicode | ||
| 180 | ComposedCharIter iter = new ComposedCharIter(); | ||
| 181 | int c; | ||
| 182 | while ((c = iter.next()) != ComposedCharIter.DONE) { | ||
| 183 | if (getCharOrder(c) == RBCollationTables.UNMAPPED) { | ||
| 184 | // | ||
| 185 | // We don't already have an ordering for this pre-composed character. | ||
| 186 | // | ||
| 187 | // First, see if the decomposed string is already in our | ||
| 188 | // tables as a single contracting-string ordering. | ||
| 189 | // If so, just map the precomposed character to that order. | ||
| 190 | // | ||
| 191 | // TODO: What we should really be doing here is trying to find the | ||
| 192 | // longest initial substring of the decomposition that is present | ||
| 193 | // in the tables as a contracting character sequence, and find its | ||
| 194 | // ordering. Then do this recursively with the remaining chars | ||
| 195 | // so that we build a list of orderings, and add that list to | ||
| 196 | // the expansion table. | ||
| 197 | // That would be more correct but also significantly slower, so | ||
| 198 | // I'm not totally sure it's worth doing. | ||
| 199 | // | ||
| 200 | String s = iter.decomposition(); | ||
| 201 | |||
| 202 | //sherman/Note: if this is 1 character decomposed string, the | ||
| 203 | //only thing need to do is to check if this decomposed character | ||
| 204 | //has an entry in our order table, this order is not necessary | ||
| 205 | //to be a contraction order, if it does have one, add an entry | ||
| 206 | //for the precomposed character by using the same order, the | ||
| 207 | //previous impl unnecessarily adds a single character expansion | ||
| 208 | //entry. | ||
| 209 | if (s.length() == 1) { | ||
| 210 | int order = getCharOrder(s.charAt(0)); | ||
| 211 | if (order != RBCollationTables.UNMAPPED) { | ||
| 212 | addOrder(c, order); | ||
| 213 | } | ||
| 214 | continue; | ||
| 215 | } else if (s.length() == 2) { | ||
| 216 | char ch0 = s.charAt(0); | ||
| 217 | if (Character.isHighSurrogate(ch0)) { | ||
| 218 | int order = getCharOrder(s.codePointAt(0)); | ||
| 219 | if (order != RBCollationTables.UNMAPPED) { | ||
| 220 | addOrder(c, order); | ||
| 221 | } | ||
| 222 | continue; | ||
| 223 | } | ||
| 224 | } | ||
| 225 | int contractOrder = getContractOrder(s); | ||
| 226 | if (contractOrder != RBCollationTables.UNMAPPED) { | ||
| 227 | addOrder(c, contractOrder); | ||
| 228 | } else { | ||
| 229 | // | ||
| 230 | // We don't have a contracting ordering for the entire string | ||
| 231 | // that results from the decomposition, but if we have orders | ||
| 232 | // for each individual character, we can add an expanding | ||
| 233 | // table entry for the pre-composed character | ||
| 234 | // | ||
| 235 | boolean allThere = true; | ||
| 236 | for (int i = 0; i < s.length(); i++) { | ||
| 237 | if (getCharOrder(s.charAt(i)) == RBCollationTables.UNMAPPED) { | ||
| 238 | allThere = false; | ||
| 239 | break; | ||
| 240 | } | ||
| 241 | } | ||
| 242 | if (allThere) { | ||
| 243 | addExpandOrder(c, s, RBCollationTables.UNMAPPED); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | } | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | /** | ||
| 251 | * Look up for unmapped values in the expanded character table. | ||
| 252 | * | ||
| 253 | * When the expanding character tables are built by addExpandOrder, | ||
| 254 | * it doesn't know what the final ordering of each character | ||
| 255 | * in the expansion will be. Instead, it just puts the raw character | ||
| 256 | * code into the table, adding CHARINDEX as a flag. Now that we've | ||
| 257 | * finished building the mapping table, we can go back and look up | ||
| 258 | * that character to see what its real collation order is and | ||
| 259 | * stick that into the expansion table. That lets us avoid doing | ||
| 260 | * a two-stage lookup later. | ||
| 261 | */ | ||
| 262 | private final void commit() | ||
| 263 | { | ||
| 264 | if (expandTable != null) { | ||
| 265 | for (int i = 0; i < expandTable.size(); i++) { | ||
| 266 | int[] valueList = (int [])expandTable.elementAt(i); | ||
| 267 | for (int j = 0; j < valueList.length; j++) { | ||
| 268 | int order = valueList[j]; | ||
| 269 | if (order < RBCollationTables.EXPANDCHARINDEX && order > CHARINDEX) { | ||
| 270 | // found a expanding character that isn't filled in yet | ||
| 271 | int ch = order - CHARINDEX; | ||
| 272 | |||
| 273 | // Get the real values for the non-filled entry | ||
| 274 | int realValue = getCharOrder(ch); | ||
| 275 | |||
| 276 | if (realValue == RBCollationTables.UNMAPPED) { | ||
| 277 | // The real value is still unmapped, maybe it's ignorable | ||
| 278 | valueList[j] = IGNORABLEMASK & ch; | ||
| 279 | } else { | ||
| 280 | // just fill in the value | ||
| 281 | valueList[j] = realValue; | ||
| 282 | } | ||
| 283 | } | ||
| 284 | } | ||
| 285 | } | ||
| 286 | } | ||
| 287 | } | ||
| 288 | /** | ||
| 289 | * Increment of the last order based on the comparison level. | ||
| 290 | */ | ||
| 291 | private final int increment(int aStrength, int lastValue) | ||
| 292 | { | ||
| 293 | switch(aStrength) | ||
| 294 | { | ||
| 295 | case Collator.PRIMARY: | ||
| 296 | // increment priamry order and mask off secondary and tertiary difference | ||
| 297 | lastValue += PRIMARYORDERINCREMENT; | ||
| 298 | lastValue &= RBCollationTables.PRIMARYORDERMASK; | ||
| 299 | isOverIgnore = true; | ||
| 300 | break; | ||
| 301 | case Collator.SECONDARY: | ||
| 302 | // increment secondary order and mask off tertiary difference | ||
| 303 | lastValue += SECONDARYORDERINCREMENT; | ||
| 304 | lastValue &= RBCollationTables.SECONDARYDIFFERENCEONLY; | ||
| 305 | // record max # of ignorable chars with secondary difference | ||
| 306 | if (!isOverIgnore) | ||
| 307 | maxSecOrder++; | ||
| 308 | break; | ||
| 309 | case Collator.TERTIARY: | ||
| 310 | // increment tertiary order | ||
| 311 | lastValue += TERTIARYORDERINCREMENT; | ||
| 312 | // record max # of ignorable chars with tertiary difference | ||
| 313 | if (!isOverIgnore) | ||
| 314 | maxTerOrder++; | ||
| 315 | break; | ||
| 316 | } | ||
| 317 | return lastValue; | ||
| 318 | } | ||
| 319 | |||
| 320 | /** | ||
| 321 | * Adds a character and its designated order into the collation table. | ||
| 322 | */ | ||
| 323 | private final void addOrder(int ch, int anOrder) | ||
| 324 | { | ||
| 325 | // See if the char already has an order in the mapping table | ||
| 326 | int order = mapping.elementAt(ch); | ||
| 327 | |||
| 328 | if (order >= RBCollationTables.CONTRACTCHARINDEX) { | ||
| 329 | // There's already an entry for this character that points to a contracting | ||
| 330 | // character table. Instead of adding the character directly to the mapping | ||
| 331 | // table, we must add it to the contract table instead. | ||
| 332 | int length = 1; | ||
| 333 | if (Character.isSupplementaryCodePoint(ch)) { | ||
| 334 | length = Character.toChars(ch, keyBuf, 0); | ||
| 335 | } else { | ||
| 336 | keyBuf[0] = (char)ch; | ||
| 337 | } | ||
| 338 | addContractOrder(new String(keyBuf, 0, length), anOrder); | ||
| 339 | } else { | ||
| 340 | // add the entry to the mapping table, | ||
| 341 | // the same later entry replaces the previous one | ||
| 342 | mapping.setElementAt(ch, anOrder); | ||
| 343 | } | ||
| 344 | } | ||
| 345 | |||
| 346 | private final void addContractOrder(String groupChars, int anOrder) { | ||
| 347 | addContractOrder(groupChars, anOrder, true); | ||
| 348 | } | ||
| 349 | |||
| 350 | /** | ||
| 351 | * Adds the contracting string into the collation table. | ||
| 352 | */ | ||
| 353 | private final void addContractOrder(String groupChars, int anOrder, | ||
| 354 | boolean fwd) | ||
| 355 | { | ||
| 356 | if (contractTable == null) { | ||
| 357 | contractTable = new Vector(INITIALTABLESIZE); | ||
| 358 | } | ||
| 359 | |||
| 360 | //initial character | ||
| 361 | int ch = groupChars.codePointAt(0); | ||
| 362 | /* | ||
| 363 | char ch0 = groupChars.charAt(0); | ||
| 364 | int ch = Character.isHighSurrogate(ch0)? | ||
| 365 | Character.toCodePoint(ch0, groupChars.charAt(1)):ch0; | ||
| 366 | */ | ||
| 367 | // See if the initial character of the string already has a contract table. | ||
| 368 | int entry = mapping.elementAt(ch); | ||
| 369 | Vector entryTable = getContractValuesImpl(entry - RBCollationTables.CONTRACTCHARINDEX); | ||
| 370 | |||
| 371 | if (entryTable == null) { | ||
| 372 | // We need to create a new table of contract entries for this base char | ||
| 373 | int tableIndex = RBCollationTables.CONTRACTCHARINDEX + contractTable.size(); | ||
| 374 | entryTable = new Vector(INITIALTABLESIZE); | ||
| 375 | contractTable.addElement(entryTable); | ||
| 376 | |||
| 377 | // Add the initial character's current ordering first. then | ||
| 378 | // update its mapping to point to this contract table | ||
| 379 | entryTable.addElement(new EntryPair(groupChars.substring(0,Character.charCount(ch)), entry)); | ||
| 380 | mapping.setElementAt(ch, tableIndex); | ||
| 381 | } | ||
| 382 | |||
| 383 | // Now add (or replace) this string in the table | ||
| 384 | int index = RBCollationTables.getEntry(entryTable, groupChars, fwd); | ||
| 385 | if (index != RBCollationTables.UNMAPPED) { | ||
| 386 | EntryPair pair = (EntryPair) entryTable.elementAt(index); | ||
| 387 | pair.value = anOrder; | ||
| 388 | } else { | ||
| 389 | EntryPair pair = (EntryPair)entryTable.lastElement(); | ||
| 390 | |||
| 391 | // NOTE: This little bit of logic is here to speed CollationElementIterator | ||
| 392 | // .nextContractChar(). This code ensures that the longest sequence in | ||
| 393 | // this list is always the _last_ one in the list. This keeps | ||
| 394 | // nextContractChar() from having to search the entire list for the longest | ||
| 395 | // sequence. | ||
| 396 | if (groupChars.length() > pair.entryName.length()) { | ||
| 397 | entryTable.addElement(new EntryPair(groupChars, anOrder, fwd)); | ||
| 398 | } else { | ||
| 399 | entryTable.insertElementAt(new EntryPair(groupChars, anOrder, | ||
| 400 | fwd), entryTable.size() - 1); | ||
| 401 | } | ||
| 402 | } | ||
| 403 | |||
| 404 | // If this was a forward mapping for a contracting string, also add a | ||
| 405 | // reverse mapping for it, so that CollationElementIterator.previous | ||
| 406 | // can work right | ||
| 407 | if (fwd && groupChars.length() > 1) { | ||
| 408 | addContractFlags(groupChars); | ||
| 409 | addContractOrder(new StringBuffer(groupChars).reverse().toString(), | ||
| 410 | anOrder, false); | ||
| 411 | } | ||
| 412 | } | ||
| 413 | |||
| 414 | /** | ||
| 415 | * If the given string has been specified as a contracting string | ||
| 416 | * in this collation table, return its ordering. | ||
| 417 | * Otherwise return UNMAPPED. | ||
| 418 | */ | ||
| 419 | private int getContractOrder(String groupChars) | ||
| 420 | { | ||
| 421 | int result = RBCollationTables.UNMAPPED; | ||
| 422 | if (contractTable != null) { | ||
| 423 | int ch = groupChars.codePointAt(0); | ||
| 424 | /* | ||
| 425 | char ch0 = groupChars.charAt(0); | ||
| 426 | int ch = Character.isHighSurrogate(ch0)? | ||
| 427 | Character.toCodePoint(ch0, groupChars.charAt(1)):ch0; | ||
| 428 | */ | ||
| 429 | Vector entryTable = getContractValues(ch); | ||
| 430 | if (entryTable != null) { | ||
| 431 | int index = RBCollationTables.getEntry(entryTable, groupChars, true); | ||
| 432 | if (index != RBCollationTables.UNMAPPED) { | ||
| 433 | EntryPair pair = (EntryPair) entryTable.elementAt(index); | ||
| 434 | result = pair.value; | ||
| 435 | } | ||
| 436 | } | ||
| 437 | } | ||
| 438 | return result; | ||
| 439 | } | ||
| 440 | |||
| 441 | private final int getCharOrder(int ch) { | ||
| 442 | int order = mapping.elementAt(ch); | ||
| 443 | |||
| 444 | if (order >= RBCollationTables.CONTRACTCHARINDEX) { | ||
| 445 | Vector groupList = getContractValuesImpl(order - RBCollationTables.CONTRACTCHARINDEX); | ||
| 446 | EntryPair pair = (EntryPair)groupList.firstElement(); | ||
| 447 | order = pair.value; | ||
| 448 | } | ||
| 449 | return order; | ||
| 450 | } | ||
| 451 | |||
| 452 | /** | ||
| 453 | * Get the entry of hash table of the contracting string in the collation | ||
| 454 | * table. | ||
| 455 | * @param ch the starting character of the contracting string | ||
| 456 | */ | ||
| 457 | private Vector getContractValues(int ch) | ||
| 458 | { | ||
| 459 | int index = mapping.elementAt(ch); | ||
| 460 | return getContractValuesImpl(index - RBCollationTables.CONTRACTCHARINDEX); | ||
| 461 | } | ||
| 462 | |||
| 463 | private Vector getContractValuesImpl(int index) | ||
| 464 | { | ||
| 465 | if (index >= 0) | ||
| 466 | { | ||
| 467 | return (Vector)contractTable.elementAt(index); | ||
| 468 | } | ||
| 469 | else // not found | ||
| 470 | { | ||
| 471 | return null; | ||
| 472 | } | ||
| 473 | } | ||
| 474 | |||
| 475 | /** | ||
| 476 | * Adds the expanding string into the collation table. | ||
| 477 | */ | ||
| 478 | private final void addExpandOrder(String contractChars, | ||
| 479 | String expandChars, | ||
| 480 | int anOrder) throws ParseException | ||
| 481 | { | ||
| 482 | // Create an expansion table entry | ||
| 483 | int tableIndex = addExpansion(anOrder, expandChars); | ||
| 484 | |||
| 485 | // And add its index into the main mapping table | ||
| 486 | if (contractChars.length() > 1) { | ||
| 487 | char ch = contractChars.charAt(0); | ||
| 488 | if (Character.isHighSurrogate(ch) && contractChars.length() == 2) { | ||
| 489 | char ch2 = contractChars.charAt(1); | ||
| 490 | if (Character.isLowSurrogate(ch2)) { | ||
| 491 | //only add into table when it is a legal surrogate | ||
| 492 | addOrder(Character.toCodePoint(ch, ch2), tableIndex); | ||
| 493 | } | ||
| 494 | } else { | ||
| 495 | addContractOrder(contractChars, tableIndex); | ||
| 496 | } | ||
| 497 | } else { | ||
| 498 | addOrder(contractChars.charAt(0), tableIndex); | ||
| 499 | } | ||
| 500 | } | ||
| 501 | |||
| 502 | private final void addExpandOrder(int ch, String expandChars, int anOrder) | ||
| 503 | throws ParseException | ||
| 504 | { | ||
| 505 | int tableIndex = addExpansion(anOrder, expandChars); | ||
| 506 | addOrder(ch, tableIndex); | ||
| 507 | } | ||
| 508 | |||
| 509 | /** | ||
| 510 | * Create a new entry in the expansion table that contains the orderings | ||
| 511 | * for the given characers. If anOrder is valid, it is added to the | ||
| 512 | * beginning of the expanded list of orders. | ||
| 513 | */ | ||
| 514 | private int addExpansion(int anOrder, String expandChars) { | ||
| 515 | if (expandTable == null) { | ||
| 516 | expandTable = new Vector(INITIALTABLESIZE); | ||
| 517 | } | ||
| 518 | |||
| 519 | // If anOrder is valid, we want to add it at the beginning of the list | ||
| 520 | int offset = (anOrder == RBCollationTables.UNMAPPED) ? 0 : 1; | ||
| 521 | |||
| 522 | int[] valueList = new int[expandChars.length() + offset]; | ||
| 523 | if (offset == 1) { | ||
| 524 | valueList[0] = anOrder; | ||
| 525 | } | ||
| 526 | |||
| 527 | int j = offset; | ||
| 528 | for (int i = 0; i < expandChars.length(); i++) { | ||
| 529 | char ch0 = expandChars.charAt(i); | ||
| 530 | char ch1; | ||
| 531 | int ch; | ||
| 532 | if (Character.isHighSurrogate(ch0)) { | ||
| 533 | if (++i == expandChars.length() || | ||
| 534 | !Character.isLowSurrogate(ch1=expandChars.charAt(i))) { | ||
| 535 | //ether we are missing the low surrogate or the next char | ||
| 536 | //is not a legal low surrogate, so stop loop | ||
| 537 | break; | ||
| 538 | } | ||
| 539 | ch = Character.toCodePoint(ch0, ch1); | ||
| 540 | |||
| 541 | } else { | ||
| 542 | ch = ch0; | ||
| 543 | } | ||
| 544 | |||
| 545 | int mapValue = getCharOrder(ch); | ||
| 546 | |||
| 547 | if (mapValue != RBCollationTables.UNMAPPED) { | ||
| 548 | valueList[j++] = mapValue; | ||
| 549 | } else { | ||
| 550 | // can't find it in the table, will be filled in by commit(). | ||
| 551 | valueList[j++] = CHARINDEX + ch; | ||
| 552 | } | ||
| 553 | } | ||
| 554 | if (j < valueList.length) { | ||
| 555 | //we had at least one supplementary character, the size of valueList | ||
| 556 | //is bigger than it really needs... | ||
| 557 | int[] tmpBuf = new int[j]; | ||
| 558 | while (--j >= 0) { | ||
| 559 | tmpBuf[j] = valueList[j]; | ||
| 560 | } | ||
| 561 | valueList = tmpBuf; | ||
| 562 | } | ||
| 563 | // Add the expanding char list into the expansion table. | ||
| 564 | int tableIndex = RBCollationTables.EXPANDCHARINDEX + expandTable.size(); | ||
| 565 | expandTable.addElement(valueList); | ||
| 566 | |||
| 567 | return tableIndex; | ||
| 568 | } | ||
| 569 | |||
| 570 | private void addContractFlags(String chars) { | ||
| 571 | char c0; | ||
| 572 | int c; | ||
| 573 | int len = chars.length(); | ||
| 574 | for (int i = 0; i < len; i++) { | ||
| 575 | c0 = chars.charAt(i); | ||
| 576 | c = Character.isHighSurrogate(c0) | ||
| 577 | ?Character.toCodePoint(c0, chars.charAt(++i)) | ||
| 578 | :c0; | ||
| 579 | contractFlags.put(c, 1); | ||
| 580 | } | ||
| 581 | } | ||
| 582 | |||
| 583 | // ============================================================== | ||
| 584 | // constants | ||
| 585 | // ============================================================== | ||
| 586 | final static int CHARINDEX = 0x70000000; // need look up in .commit() | ||
| 587 | |||
| 588 | private final static int IGNORABLEMASK = 0x0000ffff; | ||
| 589 | private final static int PRIMARYORDERINCREMENT = 0x00010000; | ||
| 590 | private final static int SECONDARYORDERINCREMENT = 0x00000100; | ||
| 591 | private final static int TERTIARYORDERINCREMENT = 0x00000001; | ||
| 592 | private final static int INITIALTABLESIZE = 20; | ||
| 593 | private final static int MAXKEYSIZE = 5; | ||
| 594 | |||
| 595 | // ============================================================== | ||
| 596 | // instance variables | ||
| 597 | // ============================================================== | ||
| 598 | |||
| 599 | // variables used by the build process | ||
| 600 | private RBCollationTables.BuildAPI tables = null; | ||
| 601 | private MergeCollation mPattern = null; | ||
| 602 | private boolean isOverIgnore = false; | ||
| 603 | private char[] keyBuf = new char[MAXKEYSIZE]; | ||
| 604 | private IntHashtable contractFlags = new IntHashtable(100); | ||
| 605 | |||
| 606 | // "shadow" copies of the instance variables in RBCollationTables | ||
| 607 | // (the values in these variables are copied back into RBCollationTables | ||
| 608 | // at the end of the build process) | ||
| 609 | private boolean frenchSec = false; | ||
| 610 | private boolean seAsianSwapping = false; | ||
| 611 | |||
| 612 | private UCompactIntArray mapping = null; | ||
| 613 | private Vector contractTable = null; | ||
| 614 | private Vector expandTable = null; | ||
| 615 | |||
| 616 | private short maxSecOrder = 0; | ||
| 617 | private short maxTerOrder = 0; | ||
| 618 | } | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
| @@ -0,0 +1,1148 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * | ||
| 28 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 29 | * (C) Copyright IBM Corp. 1996 - 2002 - All Rights Reserved | ||
| 30 | * | ||
| 31 | * The original version of this source code and documentation | ||
| 32 | * is copyrighted and owned by Taligent, Inc., a wholly-owned | ||
| 33 | * subsidiary of IBM. These materials are provided under terms | ||
| 34 | * of a License Agreement between Taligent and Sun. This technology | ||
| 35 | * is protected by multiple US and International patents. | ||
| 36 | * | ||
| 37 | * This notice and attribution to Taligent may not be removed. | ||
| 38 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 39 | */ | ||
| 40 | |||
| 41 | package java.text; | ||
| 42 | |||
| 43 | import java.io.BufferedInputStream; | ||
| 44 | import java.io.IOException; | ||
| 45 | import java.security.AccessController; | ||
| 46 | import java.security.PrivilegedActionException; | ||
| 47 | import java.security.PrivilegedExceptionAction; | ||
| 48 | import java.util.Vector; | ||
| 49 | import java.util.Stack; | ||
| 50 | import java.util.Hashtable; | ||
| 51 | import java.util.Enumeration; | ||
| 52 | import java.util.MissingResourceException; | ||
| 53 | import java.text.CharacterIterator; | ||
| 54 | import java.text.StringCharacterIterator; | ||
| 55 | import sun.text.CompactByteArray; | ||
| 56 | import sun.text.SupplementaryCharacterData; | ||
| 57 | |||
| 58 | /** | ||
| 59 | * <p>A subclass of BreakIterator whose behavior is specified using a list of rules.</p> | ||
| 60 | * | ||
| 61 | * <p>There are two kinds of rules, which are separated by semicolons: <i>substitutions</i> | ||
| 62 | * and <i>regular expressions.</i></p> | ||
| 63 | * | ||
| 64 | * <p>A substitution rule defines a name that can be used in place of an expression. It | ||
| 65 | * consists of a name, which is a string of characters contained in angle brackets, an equals | ||
| 66 | * sign, and an expression. (There can be no whitespace on either side of the equals sign.) | ||
| 67 | * To keep its syntactic meaning intact, the expression must be enclosed in parentheses or | ||
| 68 | * square brackets. A substitution is visible after its definition, and is filled in using | ||
| 69 | * simple textual substitution. Substitution definitions can contain other substitutions, as | ||
| 70 | * long as those substitutions have been defined first. Substitutions are generally used to | ||
| 71 | * make the regular expressions (which can get quite complex) shorted and easier to read. | ||
| 72 | * They typically define either character categories or commonly-used subexpressions.</p> | ||
| 73 | * | ||
| 74 | * <p>There is one special substitution. If the description defines a substitution | ||
| 75 | * called "<ignore>", the expression must be a [] expression, and the | ||
| 76 | * expression defines a set of characters (the "<em>ignore characters</em>") that | ||
| 77 | * will be transparent to the BreakIterator. A sequence of characters will break the | ||
| 78 | * same way it would if any ignore characters it contains are taken out. Break | ||
| 79 | * positions never occur befoer ignore characters.</p> | ||
| 80 | * | ||
| 81 | * <p>A regular expression uses a subset of the normal Unix regular-expression syntax, and | ||
| 82 | * defines a sequence of characters to be kept together. With one significant exception, the | ||
| 83 | * iterator uses a longest-possible-match algorithm when matching text to regular | ||
| 84 | * expressions. The iterator also treats descriptions containing multiple regular expressions | ||
| 85 | * as if they were ORed together (i.e., as if they were separated by |).</p> | ||
| 86 | * | ||
| 87 | * <p>The special characters recognized by the regular-expression parser are as follows:</p> | ||
| 88 | * | ||
| 89 | * <blockquote> | ||
| 90 | * <table border="1" width="100%"> | ||
| 91 | * <tr> | ||
| 92 | * <td width="6%">*</td> | ||
| 93 | * <td width="94%">Specifies that the expression preceding the asterisk may occur any number | ||
| 94 | * of times (including not at all).</td> | ||
| 95 | * </tr> | ||
| 96 | * <tr> | ||
| 97 | * <td width="6%">{}</td> | ||
| 98 | * <td width="94%">Encloses a sequence of characters that is optional.</td> | ||
| 99 | * </tr> | ||
| 100 | * <tr> | ||
| 101 | * <td width="6%">()</td> | ||
| 102 | * <td width="94%">Encloses a sequence of characters. If followed by *, the sequence | ||
| 103 | * repeats. Otherwise, the parentheses are just a grouping device and a way to delimit | ||
| 104 | * the ends of expressions containing |.</td> | ||
| 105 | * </tr> | ||
| 106 | * <tr> | ||
| 107 | * <td width="6%">|</td> | ||
| 108 | * <td width="94%">Separates two alternative sequences of characters. Either one | ||
| 109 | * sequence or the other, but not both, matches this expression. The | character can | ||
| 110 | * only occur inside ().</td> | ||
| 111 | * </tr> | ||
| 112 | * <tr> | ||
| 113 | * <td width="6%">.</td> | ||
| 114 | * <td width="94%">Matches any character.</td> | ||
| 115 | * </tr> | ||
| 116 | * <tr> | ||
| 117 | * <td width="6%">*?</td> | ||
| 118 | * <td width="94%">Specifies a non-greedy asterisk. *? works the same way as *, except | ||
| 119 | * when there is overlap between the last group of characters in the expression preceding the | ||
| 120 | * * and the first group of characters following the *. When there is this kind of | ||
| 121 | * overlap, * will match the longest sequence of characters that match the expression before | ||
| 122 | * the *, and *? will match the shortest sequence of characters matching the expression | ||
| 123 | * before the *?. For example, if you have "xxyxyyyxyxyxxyxyxyy" in the text, | ||
| 124 | * "x[xy]*x" will match through to the last x (i.e., "<strong>xxyxyyyxyxyxxyxyx</strong>yy", | ||
| 125 | * but "x[xy]*?x" will only match the first two xes ("<strong>xx</strong>yxyyyxyxyxxyxyxyy").</td> | ||
| 126 | * </tr> | ||
| 127 | * <tr> | ||
| 128 | * <td width="6%">[]</td> | ||
| 129 | * <td width="94%">Specifies a group of alternative characters. A [] expression will | ||
| 130 | * match any single character that is specified in the [] expression. For more on the | ||
| 131 | * syntax of [] expressions, see below.</td> | ||
| 132 | * </tr> | ||
| 133 | * <tr> | ||
| 134 | * <td width="6%">/</td> | ||
| 135 | * <td width="94%">Specifies where the break position should go if text matches this | ||
| 136 | * expression. (e.g., "[a-z]*/[:Zs:]*[1-0]" will match if the iterator sees a run | ||
| 137 | * of letters, followed by a run of whitespace, followed by a digit, but the break position | ||
| 138 | * will actually go before the whitespace). Expressions that don't contain / put the | ||
| 139 | * break position at the end of the matching text.</td> | ||
| 140 | * </tr> | ||
| 141 | * <tr> | ||
| 142 | * <td width="6%">\</td> | ||
| 143 | * <td width="94%">Escape character. The \ itself is ignored, but causes the next | ||
| 144 | * character to be treated as literal character. This has no effect for many | ||
| 145 | * characters, but for the characters listed above, this deprives them of their special | ||
| 146 | * meaning. (There are no special escape sequences for Unicode characters, or tabs and | ||
| 147 | * newlines; these are all handled by a higher-level protocol. In a Java string, | ||
| 148 | * "\n" will be converted to a literal newline character by the time the | ||
| 149 | * regular-expression parser sees it. Of course, this means that \ sequences that are | ||
| 150 | * visible to the regexp parser must be written as \\ when inside a Java string.) All | ||
| 151 | * characters in the ASCII range except for letters, digits, and control characters are | ||
| 152 | * reserved characters to the parser and must be preceded by \ even if they currently don't | ||
| 153 | * mean anything.</td> | ||
| 154 | * </tr> | ||
| 155 | * <tr> | ||
| 156 | * <td width="6%">!</td> | ||
| 157 | * <td width="94%">If ! appears at the beginning of a regular expression, it tells the regexp | ||
| 158 | * parser that this expression specifies the backwards-iteration behavior of the iterator, | ||
| 159 | * and not its normal iteration behavior. This is generally only used in situations | ||
| 160 | * where the automatically-generated backwards-iteration brhavior doesn't produce | ||
| 161 | * satisfactory results and must be supplemented with extra client-specified rules.</td> | ||
| 162 | * </tr> | ||
| 163 | * <tr> | ||
| 164 | * <td width="6%"><em>(all others)</em></td> | ||
| 165 | * <td width="94%">All other characters are treated as literal characters, which must match | ||
| 166 | * the corresponding character(s) in the text exactly.</td> | ||
| 167 | * </tr> | ||
| 168 | * </table> | ||
| 169 | * </blockquote> | ||
| 170 | * | ||
| 171 | * <p>Within a [] expression, a number of other special characters can be used to specify | ||
| 172 | * groups of characters:</p> | ||
| 173 | * | ||
| 174 | * <blockquote> | ||
| 175 | * <table border="1" width="100%"> | ||
| 176 | * <tr> | ||
| 177 | * <td width="6%">-</td> | ||
| 178 | * <td width="94%">Specifies a range of matching characters. For example | ||
| 179 | * "[a-p]" matches all lowercase Latin letters from a to p (inclusive). The - | ||
| 180 | * sign specifies ranges of continuous Unicode numeric values, not ranges of characters in a | ||
| 181 | * language's alphabetical order: "[a-z]" doesn't include capital letters, nor does | ||
| 182 | * it include accented letters such as a-umlaut.</td> | ||
| 183 | * </tr> | ||
| 184 | * <tr> | ||
| 185 | * <td width="6%">::</td> | ||
| 186 | * <td width="94%">A pair of colons containing a one- or two-letter code matches all | ||
| 187 | * characters in the corresponding Unicode category. The two-letter codes are the same | ||
| 188 | * as the two-letter codes in the Unicode database (for example, "[:Sc::Sm:]" | ||
| 189 | * matches all currency symbols and all math symbols). Specifying a one-letter code is | ||
| 190 | * the same as specifying all two-letter codes that begin with that letter (for example, | ||
| 191 | * "[:L:]" matches all letters, and is equivalent to | ||
| 192 | * "[:Lu::Ll::Lo::Lm::Lt:]"). Anything other than a valid two-letter Unicode | ||
| 193 | * category code or a single letter that begins a Unicode category code is illegal within | ||
| 194 | * colons.</td> | ||
| 195 | * </tr> | ||
| 196 | * <tr> | ||
| 197 | * <td width="6%">[]</td> | ||
| 198 | * <td width="94%">[] expressions can nest. This has no effect, except when used in | ||
| 199 | * conjunction with the ^ token.</td> | ||
| 200 | * </tr> | ||
| 201 | * <tr> | ||
| 202 | * <td width="6%">^</td> | ||
| 203 | * <td width="94%">Excludes the character (or the characters in the [] expression) following | ||
| 204 | * it from the group of characters. For example, "[a-z^p]" matches all Latin | ||
| 205 | * lowercase letters except p. "[:L:^[\u4e00-\u9fff]]" matches all letters | ||
| 206 | * except the Han ideographs.</td> | ||
| 207 | * </tr> | ||
| 208 | * <tr> | ||
| 209 | * <td width="6%"><em>(all others)</em></td> | ||
| 210 | * <td width="94%">All other characters are treated as literal characters. (For | ||
| 211 | * example, "[aeiou]" specifies just the letters a, e, i, o, and u.)</td> | ||
| 212 | * </tr> | ||
| 213 | * </table> | ||
| 214 | * </blockquote> | ||
| 215 | * | ||
| 216 | * <p>For a more complete explanation, see <a | ||
| 217 | * href="http://www.ibm.com/java/education/boundaries/boundaries.html">http://www.ibm.com/java/education/boundaries/boundaries.html</a>. | ||
| 218 | * For examples, see the resource data (which is annotated).</p> | ||
| 219 | * | ||
| 220 | * @author Richard Gillam | ||
| 221 | */ | ||
| 222 | class RuleBasedBreakIterator extends BreakIterator { | ||
| 223 | |||
| 224 | /** | ||
| 225 | * A token used as a character-category value to identify ignore characters | ||
| 226 | */ | ||
| 227 | protected static final byte IGNORE = -1; | ||
| 228 | |||
| 229 | /** | ||
| 230 | * The state number of the starting state | ||
| 231 | */ | ||
| 232 | private static final short START_STATE = 1; | ||
| 233 | |||
| 234 | /** | ||
| 235 | * The state-transition value indicating "stop" | ||
| 236 | */ | ||
| 237 | private static final short STOP_STATE = 0; | ||
| 238 | |||
| 239 | /** | ||
| 240 | * Magic number for the BreakIterator data file format. | ||
| 241 | */ | ||
| 242 | static final byte[] LABEL = { | ||
| 243 | (byte)'B', (byte)'I', (byte)'d', (byte)'a', (byte)'t', (byte)'a', | ||
| 244 | (byte)'\0' | ||
| 245 | }; | ||
| 246 | static final int LABEL_LENGTH = LABEL.length; | ||
| 247 | |||
| 248 | /** | ||
| 249 | * Version number of the dictionary that was read in. | ||
| 250 | */ | ||
| 251 | static final byte supportedVersion = 1; | ||
| 252 | |||
| 253 | /** | ||
| 254 | * Header size in byte count | ||
| 255 | */ | ||
| 256 | private static final int HEADER_LENGTH = 36; | ||
| 257 | |||
| 258 | /** | ||
| 259 | * An array length of indices for BMP characters | ||
| 260 | */ | ||
| 261 | private static final int BMP_INDICES_LENGTH = 512; | ||
| 262 | |||
| 263 | /** | ||
| 264 | * Tables that indexes from character values to character category numbers | ||
| 265 | */ | ||
| 266 | private CompactByteArray charCategoryTable = null; | ||
| 267 | private SupplementaryCharacterData supplementaryCharCategoryTable = null; | ||
| 268 | |||
| 269 | /** | ||
| 270 | * The table of state transitions used for forward iteration | ||
| 271 | */ | ||
| 272 | private short[] stateTable = null; | ||
| 273 | |||
| 274 | /** | ||
| 275 | * The table of state transitions used to sync up the iterator with the | ||
| 276 | * text in backwards and random-access iteration | ||
| 277 | */ | ||
| 278 | private short[] backwardsStateTable = null; | ||
| 279 | |||
| 280 | /** | ||
| 281 | * A list of flags indicating which states in the state table are accepting | ||
| 282 | * ("end") states | ||
| 283 | */ | ||
| 284 | private boolean[] endStates = null; | ||
| 285 | |||
| 286 | /** | ||
| 287 | * A list of flags indicating which states in the state table are | ||
| 288 | * lookahead states (states which turn lookahead on and off) | ||
| 289 | */ | ||
| 290 | private boolean[] lookaheadStates = null; | ||
| 291 | |||
| 292 | /** | ||
| 293 | * A table for additional data. May be used by a subclass of | ||
| 294 | * RuleBasedBreakIterator. | ||
| 295 | */ | ||
| 296 | private byte[] additionalData = null; | ||
| 297 | |||
| 298 | /** | ||
| 299 | * The number of character categories (and, thus, the number of columns in | ||
| 300 | * the state tables) | ||
| 301 | */ | ||
| 302 | private int numCategories; | ||
| 303 | |||
| 304 | /** | ||
| 305 | * The character iterator through which this BreakIterator accesses the text | ||
| 306 | */ | ||
| 307 | private CharacterIterator text = null; | ||
| 308 | |||
| 309 | /** | ||
| 310 | * A CRC32 value of all data in datafile | ||
| 311 | */ | ||
| 312 | private long checksum; | ||
| 313 | |||
| 314 | //======================================================================= | ||
| 315 | // constructors | ||
| 316 | //======================================================================= | ||
| 317 | |||
| 318 | /** | ||
| 319 | * Constructs a RuleBasedBreakIterator according to the datafile | ||
| 320 | * provided. | ||
| 321 | */ | ||
| 322 | public RuleBasedBreakIterator(String datafile) | ||
| 323 | throws IOException, MissingResourceException { | ||
| 324 | readTables(datafile); | ||
| 325 | } | ||
| 326 | |||
| 327 | /** | ||
| 328 | * Read datafile. The datafile's format is as follows: | ||
| 329 | * <pre> | ||
| 330 | * BreakIteratorData { | ||
| 331 | * u1 magic[7]; | ||
| 332 | * u1 version; | ||
| 333 | * u4 totalDataSize; | ||
| 334 | * header_info header; | ||
| 335 | * body value; | ||
| 336 | * } | ||
| 337 | * </pre> | ||
| 338 | * <code>totalDataSize</code> is the summation of the size of | ||
| 339 | * <code>header_info</code> and <code>body</code> in byte count. | ||
| 340 | * <p> | ||
| 341 | * In <code>header</code>, each field except for checksum implies the | ||
| 342 | * length of each field. Since <code>BMPdataLength</code> is a fixed-length | ||
| 343 | * data(512 entries), its length isn't included in <code>header</code>. | ||
| 344 | * <code>checksum</code> is a CRC32 value of all in <code>body</code>. | ||
| 345 | * <pre> | ||
| 346 | * header_info { | ||
| 347 | * u4 stateTableLength; | ||
| 348 | * u4 backwardsStateTableLength; | ||
| 349 | * u4 endStatesLength; | ||
| 350 | * u4 lookaheadStatesLength; | ||
| 351 | * u4 BMPdataLength; | ||
| 352 | * u4 nonBMPdataLength; | ||
| 353 | * u4 additionalDataLength; | ||
| 354 | * u8 checksum; | ||
| 355 | * } | ||
| 356 | * </pre> | ||
| 357 | * <p> | ||
| 358 | * | ||
| 359 | * Finally, <code>BMPindices</code> and <code>BMPdata</code> are set to | ||
| 360 | * <code>charCategoryTable</code>. <code>nonBMPdata</code> is set to | ||
| 361 | * <code>supplementaryCharCategoryTable</code>. | ||
| 362 | * <pre> | ||
| 363 | * body { | ||
| 364 | * u2 stateTable[stateTableLength]; | ||
| 365 | * u2 backwardsStateTable[backwardsStateTableLength]; | ||
| 366 | * u1 endStates[endStatesLength]; | ||
| 367 | * u1 lookaheadStates[lookaheadStatesLength]; | ||
| 368 | * u2 BMPindices[512]; | ||
| 369 | * u1 BMPdata[BMPdataLength]; | ||
| 370 | * u4 nonBMPdata[numNonBMPdataLength]; | ||
| 371 | * u1 additionalData[additionalDataLength]; | ||
| 372 | * } | ||
| 373 | * </pre> | ||
| 374 | */ | ||
| 375 | protected void readTables(String datafile) | ||
| 376 | throws IOException, MissingResourceException { | ||
| 377 | |||
| 378 | byte[] buffer = readFile(datafile); | ||
| 379 | |||
| 380 | /* Read header_info. */ | ||
| 381 | int stateTableLength = BreakIterator.getInt(buffer, 0); | ||
| 382 | int backwardsStateTableLength = BreakIterator.getInt(buffer, 4); | ||
| 383 | int endStatesLength = BreakIterator.getInt(buffer, 8); | ||
| 384 | int lookaheadStatesLength = BreakIterator.getInt(buffer, 12); | ||
| 385 | int BMPdataLength = BreakIterator.getInt(buffer, 16); | ||
| 386 | int nonBMPdataLength = BreakIterator.getInt(buffer, 20); | ||
| 387 | int additionalDataLength = BreakIterator.getInt(buffer, 24); | ||
| 388 | checksum = BreakIterator.getLong(buffer, 28); | ||
| 389 | |||
| 390 | /* Read stateTable[numCategories * numRows] */ | ||
| 391 | stateTable = new short[stateTableLength]; | ||
| 392 | int offset = HEADER_LENGTH; | ||
| 393 | for (int i = 0; i < stateTableLength; i++, offset+=2) { | ||
| 394 | stateTable[i] = BreakIterator.getShort(buffer, offset); | ||
| 395 | } | ||
| 396 | |||
| 397 | /* Read backwardsStateTable[numCategories * numRows] */ | ||
| 398 | backwardsStateTable = new short[backwardsStateTableLength]; | ||
| 399 | for (int i = 0; i < backwardsStateTableLength; i++, offset+=2) { | ||
| 400 | backwardsStateTable[i] = BreakIterator.getShort(buffer, offset); | ||
| 401 | } | ||
| 402 | |||
| 403 | /* Read endStates[numRows] */ | ||
| 404 | endStates = new boolean[endStatesLength]; | ||
| 405 | for (int i = 0; i < endStatesLength; i++, offset++) { | ||
| 406 | endStates[i] = buffer[offset] == 1; | ||
| 407 | } | ||
| 408 | |||
| 409 | /* Read lookaheadStates[numRows] */ | ||
| 410 | lookaheadStates = new boolean[lookaheadStatesLength]; | ||
| 411 | for (int i = 0; i < lookaheadStatesLength; i++, offset++) { | ||
| 412 | lookaheadStates[i] = buffer[offset] == 1; | ||
| 413 | } | ||
| 414 | |||
| 415 | /* Read a category table and indices for BMP characters. */ | ||
| 416 | short[] temp1 = new short[BMP_INDICES_LENGTH]; // BMPindices | ||
| 417 | for (int i = 0; i < BMP_INDICES_LENGTH; i++, offset+=2) { | ||
| 418 | temp1[i] = BreakIterator.getShort(buffer, offset); | ||
| 419 | } | ||
| 420 | byte[] temp2 = new byte[BMPdataLength]; // BMPdata | ||
| 421 | System.arraycopy(buffer, offset, temp2, 0, BMPdataLength); | ||
| 422 | offset += BMPdataLength; | ||
| 423 | charCategoryTable = new CompactByteArray(temp1, temp2); | ||
| 424 | |||
| 425 | /* Read a category table for non-BMP characters. */ | ||
| 426 | int[] temp3 = new int[nonBMPdataLength]; | ||
| 427 | for (int i = 0; i < nonBMPdataLength; i++, offset+=4) { | ||
| 428 | temp3[i] = BreakIterator.getInt(buffer, offset); | ||
| 429 | } | ||
| 430 | supplementaryCharCategoryTable = new SupplementaryCharacterData(temp3); | ||
| 431 | |||
| 432 | /* Read additional data */ | ||
| 433 | if (additionalDataLength > 0) { | ||
| 434 | additionalData = new byte[additionalDataLength]; | ||
| 435 | System.arraycopy(buffer, offset, additionalData, 0, additionalDataLength); | ||
| 436 | } | ||
| 437 | |||
| 438 | /* Set numCategories */ | ||
| 439 | numCategories = stateTable.length / endStates.length; | ||
| 440 | } | ||
| 441 | |||
| 442 | protected byte[] readFile(final String datafile) | ||
| 443 | throws IOException, MissingResourceException { | ||
| 444 | |||
| 445 | BufferedInputStream is; | ||
| 446 | try { | ||
| 447 | is = (BufferedInputStream)AccessController.doPrivileged( | ||
| 448 | new PrivilegedExceptionAction() { | ||
| 449 | public Object run() throws Exception { | ||
| 450 | return new BufferedInputStream(getClass().getResourceAsStream("/sun/text/resources/" + datafile)); | ||
| 451 | } | ||
| 452 | } | ||
| 453 | ); | ||
| 454 | } | ||
| 455 | catch (PrivilegedActionException e) { | ||
| 456 | throw new InternalError(e.toString()); | ||
| 457 | } | ||
| 458 | |||
| 459 | int offset = 0; | ||
| 460 | |||
| 461 | /* First, read magic, version, and header_info. */ | ||
| 462 | int len = LABEL_LENGTH + 5; | ||
| 463 | byte[] buf = new byte[len]; | ||
| 464 | if (is.read(buf) != len) { | ||
| 465 | throw new MissingResourceException("Wrong header length", | ||
| 466 | datafile, ""); | ||
| 467 | } | ||
| 468 | |||
| 469 | /* Validate the magic number. */ | ||
| 470 | for (int i = 0; i < LABEL_LENGTH; i++, offset++) { | ||
| 471 | if (buf[offset] != LABEL[offset]) { | ||
| 472 | throw new MissingResourceException("Wrong magic number", | ||
| 473 | datafile, ""); | ||
| 474 | } | ||
| 475 | } | ||
| 476 | |||
| 477 | /* Validate the version number. */ | ||
| 478 | if (buf[offset] != supportedVersion) { | ||
| 479 | throw new MissingResourceException("Unsupported version(" + buf[offset] + ")", | ||
| 480 | datafile, ""); | ||
| 481 | } | ||
| 482 | |||
| 483 | /* Read data: totalDataSize + 8(for checksum) */ | ||
| 484 | len = BreakIterator.getInt(buf, ++offset); | ||
| 485 | buf = new byte[len]; | ||
| 486 | if (is.read(buf) != len) { | ||
| 487 | throw new MissingResourceException("Wrong data length", | ||
| 488 | datafile, ""); | ||
| 489 | } | ||
| 490 | |||
| 491 | is.close(); | ||
| 492 | |||
| 493 | return buf; | ||
| 494 | } | ||
| 495 | |||
| 496 | byte[] getAdditionalData() { | ||
| 497 | return additionalData; | ||
| 498 | } | ||
| 499 | |||
| 500 | void setAdditionalData(byte[] b) { | ||
| 501 | additionalData = b; | ||
| 502 | } | ||
| 503 | |||
| 504 | //======================================================================= | ||
| 505 | // boilerplate | ||
| 506 | //======================================================================= | ||
| 507 | /** | ||
| 508 | * Clones this iterator. | ||
| 509 | * @return A newly-constructed RuleBasedBreakIterator with the same | ||
| 510 | * behavior as this one. | ||
| 511 | */ | ||
| 512 | public Object clone() { | ||
| 513 | RuleBasedBreakIterator result = (RuleBasedBreakIterator) super.clone(); | ||
| 514 | if (text != null) { | ||
| 515 | result.text = (CharacterIterator) text.clone(); | ||
| 516 | } | ||
| 517 | return result; | ||
| 518 | } | ||
| 519 | |||
| 520 | /** | ||
| 521 | * Returns true if both BreakIterators are of the same class, have the same | ||
| 522 | * rules, and iterate over the same text. | ||
| 523 | */ | ||
| 524 | public boolean equals(Object that) { | ||
| 525 | try { | ||
| 526 | if (that == null) { | ||
| 527 | return false; | ||
| 528 | } | ||
| 529 | |||
| 530 | RuleBasedBreakIterator other = (RuleBasedBreakIterator) that; | ||
| 531 | if (checksum != other.checksum) { | ||
| 532 | return false; | ||
| 533 | } | ||
| 534 | if (text == null) { | ||
| 535 | return other.text == null; | ||
| 536 | } else { | ||
| 537 | return text.equals(other.text); | ||
| 538 | } | ||
| 539 | } | ||
| 540 | catch(ClassCastException e) { | ||
| 541 | return false; | ||
| 542 | } | ||
| 543 | } | ||
| 544 | |||
| 545 | /** | ||
| 546 | * Returns text | ||
| 547 | */ | ||
| 548 | public String toString() { | ||
| 549 | StringBuffer sb = new StringBuffer(); | ||
| 550 | sb.append('['); | ||
| 551 | sb.append("checksum=0x" + Long.toHexString(checksum)); | ||
| 552 | sb.append(']'); | ||
| 553 | return sb.toString(); | ||
| 554 | } | ||
| 555 | |||
| 556 | /** | ||
| 557 | * Compute a hashcode for this BreakIterator | ||
| 558 | * @return A hash code | ||
| 559 | */ | ||
| 560 | public int hashCode() { | ||
| 561 | return (int)checksum; | ||
| 562 | } | ||
| 563 | |||
| 564 | //======================================================================= | ||
| 565 | // BreakIterator overrides | ||
| 566 | //======================================================================= | ||
| 567 | |||
| 568 | /** | ||
| 569 | * Sets the current iteration position to the beginning of the text. | ||
| 570 | * (i.e., the CharacterIterator's starting offset). | ||
| 571 | * @return The offset of the beginning of the text. | ||
| 572 | */ | ||
| 573 | public int first() { | ||
| 574 | CharacterIterator t = getText(); | ||
| 575 | |||
| 576 | t.first(); | ||
| 577 | return t.getIndex(); | ||
| 578 | } | ||
| 579 | |||
| 580 | /** | ||
| 581 | * Sets the current iteration position to the end of the text. | ||
| 582 | * (i.e., the CharacterIterator's ending offset). | ||
| 583 | * @return The text's past-the-end offset. | ||
| 584 | */ | ||
| 585 | public int last() { | ||
| 586 | CharacterIterator t = getText(); | ||
| 587 | |||
| 588 | // I'm not sure why, but t.last() returns the offset of the last character, | ||
| 589 | // rather than the past-the-end offset | ||
| 590 | t.setIndex(t.getEndIndex()); | ||
| 591 | return t.getIndex(); | ||
| 592 | } | ||
| 593 | |||
| 594 | /** | ||
| 595 | * Advances the iterator either forward or backward the specified number of steps. | ||
| 596 | * Negative values move backward, and positive values move forward. This is | ||
| 597 | * equivalent to repeatedly calling next() or previous(). | ||
| 598 | * @param n The number of steps to move. The sign indicates the direction | ||
| 599 | * (negative is backwards, and positive is forwards). | ||
| 600 | * @return The character offset of the boundary position n boundaries away from | ||
| 601 | * the current one. | ||
| 602 | */ | ||
| 603 | public int next(int n) { | ||
| 604 | int result = current(); | ||
| 605 | while (n > 0) { | ||
| 606 | result = handleNext(); | ||
| 607 | --n; | ||
| 608 | } | ||
| 609 | while (n < 0) { | ||
| 610 | result = previous(); | ||
| 611 | ++n; | ||
| 612 | } | ||
| 613 | return result; | ||
| 614 | } | ||
| 615 | |||
| 616 | /** | ||
| 617 | * Advances the iterator to the next boundary position. | ||
| 618 | * @return The position of the first boundary after this one. | ||
| 619 | */ | ||
| 620 | public int next() { | ||
| 621 | return handleNext(); | ||
| 622 | } | ||
| 623 | |||
| 624 | private int cachedLastKnownBreak = BreakIterator.DONE; | ||
| 625 | |||
| 626 | /** | ||
| 627 | * Advances the iterator backwards, to the last boundary preceding this one. | ||
| 628 | * @return The position of the last boundary position preceding this one. | ||
| 629 | */ | ||
| 630 | public int previous() { | ||
| 631 | // if we're already sitting at the beginning of the text, return DONE | ||
| 632 | CharacterIterator text = getText(); | ||
| 633 | if (current() == text.getBeginIndex()) { | ||
| 634 | return BreakIterator.DONE; | ||
| 635 | } | ||
| 636 | |||
| 637 | // set things up. handlePrevious() will back us up to some valid | ||
| 638 | // break position before the current position (we back our internal | ||
| 639 | // iterator up one step to prevent handlePrevious() from returning | ||
| 640 | // the current position), but not necessarily the last one before | ||
| 641 | // where we started | ||
| 642 | int start = current(); | ||
| 643 | int lastResult = cachedLastKnownBreak; | ||
| 644 | if (lastResult >= start || lastResult <= BreakIterator.DONE) { | ||
| 645 | getPrevious(); | ||
| 646 | lastResult = handlePrevious(); | ||
| 647 | } else { | ||
| 648 | //it might be better to check if handlePrevious() give us closer | ||
| 649 | //safe value but handlePrevious() is slow too | ||
| 650 | //So, this has to be done carefully | ||
| 651 | text.setIndex(lastResult); | ||
| 652 | } | ||
| 653 | int result = lastResult; | ||
| 654 | |||
| 655 | // iterate forward from the known break position until we pass our | ||
| 656 | // starting point. The last break position before the starting | ||
| 657 | // point is our return value | ||
| 658 | while (result != BreakIterator.DONE && result < start) { | ||
| 659 | lastResult = result; | ||
| 660 | result = handleNext(); | ||
| 661 | } | ||
| 662 | |||
| 663 | // set the current iteration position to be the last break position | ||
| 664 | // before where we started, and then return that value | ||
| 665 | text.setIndex(lastResult); | ||
| 666 | cachedLastKnownBreak = lastResult; | ||
| 667 | return lastResult; | ||
| 668 | } | ||
| 669 | |||
| 670 | /** | ||
| 671 | * Returns previous character | ||
| 672 | */ | ||
| 673 | private int getPrevious() { | ||
| 674 | char c2 = text.previous(); | ||
| 675 | if (Character.isLowSurrogate(c2) && | ||
| 676 | text.getIndex() > text.getBeginIndex()) { | ||
| 677 | char c1 = text.previous(); | ||
| 678 | if (Character.isHighSurrogate(c1)) { | ||
| 679 | return Character.toCodePoint(c1, c2); | ||
| 680 | } else { | ||
| 681 | text.next(); | ||
| 682 | } | ||
| 683 | } | ||
| 684 | return (int)c2; | ||
| 685 | } | ||
| 686 | |||
| 687 | /** | ||
| 688 | * Returns current character | ||
| 689 | */ | ||
| 690 | int getCurrent() { | ||
| 691 | char c1 = text.current(); | ||
| 692 | if (Character.isHighSurrogate(c1) && | ||
| 693 | text.getIndex() < text.getEndIndex()) { | ||
| 694 | char c2 = text.next(); | ||
| 695 | text.previous(); | ||
| 696 | if (Character.isLowSurrogate(c2)) { | ||
| 697 | return Character.toCodePoint(c1, c2); | ||
| 698 | } | ||
| 699 | } | ||
| 700 | return (int)c1; | ||
| 701 | } | ||
| 702 | |||
| 703 | /** | ||
| 704 | * Returns the count of next character. | ||
| 705 | */ | ||
| 706 | private int getCurrentCodePointCount() { | ||
| 707 | char c1 = text.current(); | ||
| 708 | if (Character.isHighSurrogate(c1) && | ||
| 709 | text.getIndex() < text.getEndIndex()) { | ||
| 710 | char c2 = text.next(); | ||
| 711 | text.previous(); | ||
| 712 | if (Character.isLowSurrogate(c2)) { | ||
| 713 | return 2; | ||
| 714 | } | ||
| 715 | } | ||
| 716 | return 1; | ||
| 717 | } | ||
| 718 | |||
| 719 | /** | ||
| 720 | * Returns next character | ||
| 721 | */ | ||
| 722 | int getNext() { | ||
| 723 | int index = text.getIndex(); | ||
| 724 | int endIndex = text.getEndIndex(); | ||
| 725 | if (index == endIndex || | ||
| 726 | (index = index + getCurrentCodePointCount()) >= endIndex) { | ||
| 727 | return CharacterIterator.DONE; | ||
| 728 | } | ||
| 729 | text.setIndex(index); | ||
| 730 | return getCurrent(); | ||
| 731 | } | ||
| 732 | |||
| 733 | /** | ||
| 734 | * Returns the position of next character. | ||
| 735 | */ | ||
| 736 | private int getNextIndex() { | ||
| 737 | int index = text.getIndex() + getCurrentCodePointCount(); | ||
| 738 | int endIndex = text.getEndIndex(); | ||
| 739 | if (index > endIndex) { | ||
| 740 | return endIndex; | ||
| 741 | } else { | ||
| 742 | return index; | ||
| 743 | } | ||
| 744 | } | ||
| 745 | |||
| 746 | /** | ||
| 747 | * Throw IllegalArgumentException unless begin <= offset < end. | ||
| 748 | */ | ||
| 749 | protected static final void checkOffset(int offset, CharacterIterator text) { | ||
| 750 | if (offset < text.getBeginIndex() || offset > text.getEndIndex()) { | ||
| 751 | throw new IllegalArgumentException("offset out of bounds"); | ||
| 752 | } | ||
| 753 | } | ||
| 754 | |||
| 755 | /** | ||
| 756 | * Sets the iterator to refer to the first boundary position following | ||
| 757 | * the specified position. | ||
| 758 | * @offset The position from which to begin searching for a break position. | ||
| 759 | * @return The position of the first break after the current position. | ||
| 760 | */ | ||
| 761 | public int following(int offset) { | ||
| 762 | |||
| 763 | CharacterIterator text = getText(); | ||
| 764 | checkOffset(offset, text); | ||
| 765 | |||
| 766 | // Set our internal iteration position (temporarily) | ||
| 767 | // to the position passed in. If this is the _beginning_ position, | ||
| 768 | // then we can just use next() to get our return value | ||
| 769 | text.setIndex(offset); | ||
| 770 | if (offset == text.getBeginIndex()) { | ||
| 771 | cachedLastKnownBreak = handleNext(); | ||
| 772 | return cachedLastKnownBreak; | ||
| 773 | } | ||
| 774 | |||
| 775 | // otherwise, we have to sync up first. Use handlePrevious() to back | ||
| 776 | // us up to a known break position before the specified position (if | ||
| 777 | // we can determine that the specified position is a break position, | ||
| 778 | // we don't back up at all). This may or may not be the last break | ||
| 779 | // position at or before our starting position. Advance forward | ||
| 780 | // from here until we've passed the starting position. The position | ||
| 781 | // we stop on will be the first break position after the specified one. | ||
| 782 | int result = cachedLastKnownBreak; | ||
| 783 | if (result >= offset || result <= BreakIterator.DONE) { | ||
| 784 | result = handlePrevious(); | ||
| 785 | } else { | ||
| 786 | //it might be better to check if handlePrevious() give us closer | ||
| 787 | //safe value but handlePrevious() is slow too | ||
| 788 | //So, this has to be done carefully | ||
| 789 | text.setIndex(result); | ||
| 790 | } | ||
| 791 | while (result != BreakIterator.DONE && result <= offset) { | ||
| 792 | result = handleNext(); | ||
| 793 | } | ||
| 794 | cachedLastKnownBreak = result; | ||
| 795 | return result; | ||
| 796 | } | ||
| 797 | |||
| 798 | /** | ||
| 799 | * Sets the iterator to refer to the last boundary position before the | ||
| 800 | * specified position. | ||
| 801 | * @offset The position to begin searching for a break from. | ||
| 802 | * @return The position of the last boundary before the starting position. | ||
| 803 | */ | ||
| 804 | public int preceding(int offset) { | ||
| 805 | // if we start by updating the current iteration position to the | ||
| 806 | // position specified by the caller, we can just use previous() | ||
| 807 | // to carry out this operation | ||
| 808 | CharacterIterator text = getText(); | ||
| 809 | checkOffset(offset, text); | ||
| 810 | text.setIndex(offset); | ||
| 811 | return previous(); | ||
| 812 | } | ||
| 813 | |||
| 814 | /** | ||
| 815 | * Returns true if the specfied position is a boundary position. As a side | ||
| 816 | * effect, leaves the iterator pointing to the first boundary position at | ||
| 817 | * or after "offset". | ||
| 818 | * @param offset the offset to check. | ||
| 819 | * @return True if "offset" is a boundary position. | ||
| 820 | */ | ||
| 821 | public boolean isBoundary(int offset) { | ||
| 822 | CharacterIterator text = getText(); | ||
| 823 | checkOffset(offset, text); | ||
| 824 | if (offset == text.getBeginIndex()) { | ||
| 825 | return true; | ||
| 826 | } | ||
| 827 | |||
| 828 | // to check whether this is a boundary, we can use following() on the | ||
| 829 | // position before the specified one and return true if the position we | ||
| 830 | // get back is the one the user specified | ||
| 831 | else { | ||
| 832 | return following(offset - 1) == offset; | ||
| 833 | } | ||
| 834 | } | ||
| 835 | |||
| 836 | /** | ||
| 837 | * Returns the current iteration position. | ||
| 838 | * @return The current iteration position. | ||
| 839 | */ | ||
| 840 | public int current() { | ||
| 841 | return getText().getIndex(); | ||
| 842 | } | ||
| 843 | |||
| 844 | /** | ||
| 845 | * Return a CharacterIterator over the text being analyzed. This version | ||
| 846 | * of this method returns the actual CharacterIterator we're using internally. | ||
| 847 | * Changing the state of this iterator can have undefined consequences. If | ||
| 848 | * you need to change it, clone it first. | ||
| 849 | * @return An iterator over the text being analyzed. | ||
| 850 | */ | ||
| 851 | public CharacterIterator getText() { | ||
| 852 | // The iterator is initialized pointing to no text at all, so if this | ||
| 853 | // function is called while we're in that state, we have to fudge an | ||
| 854 | // iterator to return. | ||
| 855 | if (text == null) { | ||
| 856 | text = new StringCharacterIterator(""); | ||
| 857 | } | ||
| 858 | return text; | ||
| 859 | } | ||
| 860 | |||
| 861 | /** | ||
| 862 | * Set the iterator to analyze a new piece of text. This function resets | ||
| 863 | * the current iteration position to the beginning of the text. | ||
| 864 | * @param newText An iterator over the text to analyze. | ||
| 865 | */ | ||
| 866 | public void setText(CharacterIterator newText) { | ||
| 867 | // Test iterator to see if we need to wrap it in a SafeCharIterator. | ||
| 868 | // The correct behavior for CharacterIterators is to allow the | ||
| 869 | // position to be set to the endpoint of the iterator. Many | ||
| 870 | // CharacterIterators do not uphold this, so this is a workaround | ||
| 871 | // to permit them to use this class. | ||
| 872 | int end = newText.getEndIndex(); | ||
| 873 | boolean goodIterator; | ||
| 874 | try { | ||
| 875 | newText.setIndex(end); // some buggy iterators throw an exception here | ||
| 876 | goodIterator = newText.getIndex() == end; | ||
| 877 | } | ||
| 878 | catch(IllegalArgumentException e) { | ||
| 879 | goodIterator = false; | ||
| 880 | } | ||
| 881 | |||
| 882 | if (goodIterator) { | ||
| 883 | text = newText; | ||
| 884 | } | ||
| 885 | else { | ||
| 886 | text = new SafeCharIterator(newText); | ||
| 887 | } | ||
| 888 | text.first(); | ||
| 889 | |||
| 890 | cachedLastKnownBreak = BreakIterator.DONE; | ||
| 891 | } | ||
| 892 | |||
| 893 | |||
| 894 | //======================================================================= | ||
| 895 | // implementation | ||
| 896 | //======================================================================= | ||
| 897 | |||
| 898 | /** | ||
| 899 | * This method is the actual implementation of the next() method. All iteration | ||
| 900 | * vectors through here. This method initializes the state machine to state 1 | ||
| 901 | * and advances through the text character by character until we reach the end | ||
| 902 | * of the text or the state machine transitions to state 0. We update our return | ||
| 903 | * value every time the state machine passes through a possible end state. | ||
| 904 | */ | ||
| 905 | protected int handleNext() { | ||
| 906 | // if we're already at the end of the text, return DONE. | ||
| 907 | CharacterIterator text = getText(); | ||
| 908 | if (text.getIndex() == text.getEndIndex()) { | ||
| 909 | return BreakIterator.DONE; | ||
| 910 | } | ||
| 911 | |||
| 912 | // no matter what, we always advance at least one character forward | ||
| 913 | int result = getNextIndex(); | ||
| 914 | int lookaheadResult = 0; | ||
| 915 | |||
| 916 | // begin in state 1 | ||
| 917 | int state = START_STATE; | ||
| 918 | int category; | ||
| 919 | int c = getCurrent(); | ||
| 920 | |||
| 921 | // loop until we reach the end of the text or transition to state 0 | ||
| 922 | while (c != CharacterIterator.DONE && state != STOP_STATE) { | ||
| 923 | |||
| 924 | // look up the current character's character category (which tells us | ||
| 925 | // which column in the state table to look at) | ||
| 926 | category = lookupCategory(c); | ||
| 927 | |||
| 928 | // if the character isn't an ignore character, look up a state | ||
| 929 | // transition in the state table | ||
| 930 | if (category != IGNORE) { | ||
| 931 | state = lookupState(state, category); | ||
| 932 | } | ||
| 933 | |||
| 934 | // if the state we've just transitioned to is a lookahead state, | ||
| 935 | // (but not also an end state), save its position. If it's | ||
| 936 | // both a lookahead state and an end state, update the break position | ||
| 937 | // to the last saved lookup-state position | ||
| 938 | if (lookaheadStates[state]) { | ||
| 939 | if (endStates[state]) { | ||
| 940 | result = lookaheadResult; | ||
| 941 | } | ||
| 942 | else { | ||
| 943 | lookaheadResult = getNextIndex(); | ||
| 944 | } | ||
| 945 | } | ||
| 946 | |||
| 947 | // otherwise, if the state we've just transitioned to is an accepting | ||
| 948 | // state, update the break position to be the current iteration position | ||
| 949 | else { | ||
| 950 | if (endStates[state]) { | ||
| 951 | result = getNextIndex(); | ||
| 952 | } | ||
| 953 | } | ||
| 954 | |||
| 955 | c = getNext(); | ||
| 956 | } | ||
| 957 | |||
| 958 | // if we've run off the end of the text, and the very last character took us into | ||
| 959 | // a lookahead state, advance the break position to the lookahead position | ||
| 960 | // (the theory here is that if there are no characters at all after the lookahead | ||
| 961 | // position, that always matches the lookahead criteria) | ||
| 962 | if (c == CharacterIterator.DONE && lookaheadResult == text.getEndIndex()) { | ||
| 963 | result = lookaheadResult; | ||
| 964 | } | ||
| 965 | |||
| 966 | text.setIndex(result); | ||
| 967 | return result; | ||
| 968 | } | ||
| 969 | |||
| 970 | /** | ||
| 971 | * This method backs the iterator back up to a "safe position" in the text. | ||
| 972 | * This is a position that we know, without any context, must be a break position. | ||
| 973 | * The various calling methods then iterate forward from this safe position to | ||
| 974 | * the appropriate position to return. (For more information, see the description | ||
| 975 | * of buildBackwardsStateTable() in RuleBasedBreakIterator.Builder.) | ||
| 976 | */ | ||
| 977 | protected int handlePrevious() { | ||
| 978 | CharacterIterator text = getText(); | ||
| 979 | int state = START_STATE; | ||
| 980 | int category = 0; | ||
| 981 | int lastCategory = 0; | ||
| 982 | int c = getCurrent(); | ||
| 983 | |||
| 984 | // loop until we reach the beginning of the text or transition to state 0 | ||
| 985 | while (c != CharacterIterator.DONE && state != STOP_STATE) { | ||
| 986 | |||
| 987 | // save the last character's category and look up the current | ||
| 988 | // character's category | ||
| 989 | lastCategory = category; | ||
| 990 | category = lookupCategory(c); | ||
| 991 | |||
| 992 | // if the current character isn't an ignore character, look up a | ||
| 993 | // state transition in the backwards state table | ||
| 994 | if (category != IGNORE) { | ||
| 995 | state = lookupBackwardState(state, category); | ||
| 996 | } | ||
| 997 | |||
| 998 | // then advance one character backwards | ||
| 999 | c = getPrevious(); | ||
| 1000 | } | ||
| 1001 | |||
| 1002 | // if we didn't march off the beginning of the text, we're either one or two | ||
| 1003 | // positions away from the real break position. (One because of the call to | ||
| 1004 | // previous() at the end of the loop above, and another because the character | ||
| 1005 | // that takes us into the stop state will always be the character BEFORE | ||
| 1006 | // the break position.) | ||
| 1007 | if (c != CharacterIterator.DONE) { | ||
| 1008 | if (lastCategory != IGNORE) { | ||
| 1009 | getNext(); | ||
| 1010 | getNext(); | ||
| 1011 | } | ||
| 1012 | else { | ||
| 1013 | getNext(); | ||
| 1014 | } | ||
| 1015 | } | ||
| 1016 | return text.getIndex(); | ||
| 1017 | } | ||
| 1018 | |||
| 1019 | /** | ||
| 1020 | * Looks up a character's category (i.e., its category for breaking purposes, | ||
| 1021 | * not its Unicode category) | ||
| 1022 | */ | ||
| 1023 | protected int lookupCategory(int c) { | ||
| 1024 | if (c < Character.MIN_SUPPLEMENTARY_CODE_POINT) { | ||
| 1025 | return charCategoryTable.elementAt((char)c); | ||
| 1026 | } else { | ||
| 1027 | return supplementaryCharCategoryTable.getValue(c); | ||
| 1028 | } | ||
| 1029 | } | ||
| 1030 | |||
| 1031 | /** | ||
| 1032 | * Given a current state and a character category, looks up the | ||
| 1033 | * next state to transition to in the state table. | ||
| 1034 | */ | ||
| 1035 | protected int lookupState(int state, int category) { | ||
| 1036 | return stateTable[state * numCategories + category]; | ||
| 1037 | } | ||
| 1038 | |||
| 1039 | /** | ||
| 1040 | * Given a current state and a character category, looks up the | ||
| 1041 | * next state to transition to in the backwards state table. | ||
| 1042 | */ | ||
| 1043 | protected int lookupBackwardState(int state, int category) { | ||
| 1044 | return backwardsStateTable[state * numCategories + category]; | ||
| 1045 | } | ||
| 1046 | |||
| 1047 | /* | ||
| 1048 | * This class exists to work around a bug in incorrect implementations | ||
| 1049 | * of CharacterIterator, which incorrectly handle setIndex(endIndex). | ||
| 1050 | * This iterator relies only on base.setIndex(n) where n is less than | ||
| 1051 | * endIndex. | ||
| 1052 | * | ||
| 1053 | * One caveat: if the base iterator's begin and end indices change | ||
| 1054 | * the change will not be reflected by this wrapper. Does that matter? | ||
| 1055 | */ | ||
| 1056 | private static final class SafeCharIterator implements CharacterIterator, | ||
| 1057 | Cloneable { | ||
| 1058 | |||
| 1059 | private CharacterIterator base; | ||
| 1060 | private int rangeStart; | ||
| 1061 | private int rangeLimit; | ||
| 1062 | private int currentIndex; | ||
| 1063 | |||
| 1064 | SafeCharIterator(CharacterIterator base) { | ||
| 1065 | this.base = base; | ||
| 1066 | this.rangeStart = base.getBeginIndex(); | ||
| 1067 | this.rangeLimit = base.getEndIndex(); | ||
| 1068 | this.currentIndex = base.getIndex(); | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | public char first() { | ||
| 1072 | return setIndex(rangeStart); | ||
| 1073 | } | ||
| 1074 | |||
| 1075 | public char last() { | ||
| 1076 | return setIndex(rangeLimit - 1); | ||
| 1077 | } | ||
| 1078 | |||
| 1079 | public char current() { | ||
| 1080 | if (currentIndex < rangeStart || currentIndex >= rangeLimit) { | ||
| 1081 | return DONE; | ||
| 1082 | } | ||
| 1083 | else { | ||
| 1084 | return base.setIndex(currentIndex); | ||
| 1085 | } | ||
| 1086 | } | ||
| 1087 | |||
| 1088 | public char next() { | ||
| 1089 | |||
| 1090 | currentIndex++; | ||
| 1091 | if (currentIndex >= rangeLimit) { | ||
| 1092 | currentIndex = rangeLimit; | ||
| 1093 | return DONE; | ||
| 1094 | } | ||
| 1095 | else { | ||
| 1096 | return base.setIndex(currentIndex); | ||
| 1097 | } | ||
| 1098 | } | ||
| 1099 | |||
| 1100 | public char previous() { | ||
| 1101 | |||
| 1102 | currentIndex--; | ||
| 1103 | if (currentIndex < rangeStart) { | ||
| 1104 | currentIndex = rangeStart; | ||
| 1105 | return DONE; | ||
| 1106 | } | ||
| 1107 | else { | ||
| 1108 | return base.setIndex(currentIndex); | ||
| 1109 | } | ||
| 1110 | } | ||
| 1111 | |||
| 1112 | public char setIndex(int i) { | ||
| 1113 | |||
| 1114 | if (i < rangeStart || i > rangeLimit) { | ||
| 1115 | throw new IllegalArgumentException("Invalid position"); | ||
| 1116 | } | ||
| 1117 | currentIndex = i; | ||
| 1118 | return current(); | ||
| 1119 | } | ||
| 1120 | |||
| 1121 | public int getBeginIndex() { | ||
| 1122 | return rangeStart; | ||
| 1123 | } | ||
| 1124 | |||
| 1125 | public int getEndIndex() { | ||
| 1126 | return rangeLimit; | ||
| 1127 | } | ||
| 1128 | |||
| 1129 | public int getIndex() { | ||
| 1130 | return currentIndex; | ||
| 1131 | } | ||
| 1132 | |||
| 1133 | public Object clone() { | ||
| 1134 | |||
| 1135 | SafeCharIterator copy = null; | ||
| 1136 | try { | ||
| 1137 | copy = (SafeCharIterator) super.clone(); | ||
| 1138 | } | ||
| 1139 | catch(CloneNotSupportedException e) { | ||
| 1140 | throw new Error("Clone not supported: " + e); | ||
| 1141 | } | ||
| 1142 | |||
| 1143 | CharacterIterator copyOfBase = (CharacterIterator) base.clone(); | ||
| 1144 | copy.base = copyOfBase; | ||
| 1145 | return copy; | ||
| 1146 | } | ||
| 1147 | } | ||
| 1148 | } | ||
Binary file not shown.
| @@ -0,0 +1,123 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | /** | ||
| 42 | * A RuleBasedCollationKey is a concrete implementation of CollationKey class. | ||
| 43 | * The RuleBasedCollationKey class is used by the RuleBasedCollator class. | ||
| 44 | */ | ||
| 45 | |||
| 46 | final class RuleBasedCollationKey extends CollationKey { | ||
| 47 | /** | ||
| 48 | * Compare this RuleBasedCollationKey to target. The collation rules of the | ||
| 49 | * Collator object which created these keys are applied. <strong>Note:</strong> | ||
| 50 | * RuleBasedCollationKeys created by different Collators can not be compared. | ||
| 51 | * @param target target RuleBasedCollationKey | ||
| 52 | * @return Returns an integer value. Value is less than zero if this is less | ||
| 53 | * than target, value is zero if this and target are equal and value is greater than | ||
| 54 | * zero if this is greater than target. | ||
| 55 | * @see java.text.Collator#compare | ||
| 56 | */ | ||
| 57 | public int compareTo(CollationKey target) | ||
| 58 | { | ||
| 59 | int result = key.compareTo(((RuleBasedCollationKey)(target)).key); | ||
| 60 | if (result <= Collator.LESS) | ||
| 61 | return Collator.LESS; | ||
| 62 | else if (result >= Collator.GREATER) | ||
| 63 | return Collator.GREATER; | ||
| 64 | return Collator.EQUAL; | ||
| 65 | } | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Compare this RuleBasedCollationKey and the target for equality. | ||
| 69 | * The collation rules of the Collator object which created these keys are applied. | ||
| 70 | * <strong>Note:</strong> RuleBasedCollationKeys created by different Collators can not be | ||
| 71 | * compared. | ||
| 72 | * @param target the RuleBasedCollationKey to compare to. | ||
| 73 | * @return Returns true if two objects are equal, false otherwise. | ||
| 74 | */ | ||
| 75 | public boolean equals(Object target) { | ||
| 76 | if (this == target) return true; | ||
| 77 | if (target == null || !getClass().equals(target.getClass())) { | ||
| 78 | return false; | ||
| 79 | } | ||
| 80 | RuleBasedCollationKey other = (RuleBasedCollationKey)target; | ||
| 81 | return key.equals(other.key); | ||
| 82 | } | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Creates a hash code for this RuleBasedCollationKey. The hash value is calculated on the | ||
| 86 | * key itself, not the String from which the key was created. Thus | ||
| 87 | * if x and y are RuleBasedCollationKeys, then x.hashCode(x) == y.hashCode() if | ||
| 88 | * x.equals(y) is true. This allows language-sensitive comparison in a hash table. | ||
| 89 | * See the CollatinKey class description for an example. | ||
| 90 | * @return the hash value based on the string's collation order. | ||
| 91 | */ | ||
| 92 | public int hashCode() { | ||
| 93 | return (key.hashCode()); | ||
| 94 | } | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Converts the RuleBasedCollationKey to a sequence of bits. If two RuleBasedCollationKeys | ||
| 98 | * could be legitimately compared, then one could compare the byte arrays | ||
| 99 | * for each of those keys to obtain the same result. Byte arrays are | ||
| 100 | * organized most significant byte first. | ||
| 101 | */ | ||
| 102 | public byte[] toByteArray() { | ||
| 103 | |||
| 104 | char[] src = key.toCharArray(); | ||
| 105 | byte[] dest = new byte[ 2*src.length ]; | ||
| 106 | int j = 0; | ||
| 107 | for( int i=0; i<src.length; i++ ) { | ||
| 108 | dest[j++] = (byte)(src[i] >>> 8); | ||
| 109 | dest[j++] = (byte)(src[i] & 0x00ff); | ||
| 110 | } | ||
| 111 | return dest; | ||
| 112 | } | ||
| 113 | |||
| 114 | /** | ||
| 115 | * A RuleBasedCollationKey can only be generated by Collator objects. | ||
| 116 | */ | ||
| 117 | RuleBasedCollationKey(String source, String key) { | ||
| 118 | super(source); | ||
| 119 | this.key = key; | ||
| 120 | } | ||
| 121 | private String key = null; | ||
| 122 | |||
| 123 | } | ||
Binary file not shown.
| @@ -0,0 +1,763 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /* | ||
| 27 | * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved | ||
| 28 | * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is copyrighted | ||
| 31 | * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These | ||
| 32 | * materials are provided under terms of a License Agreement between Taligent | ||
| 33 | * and Sun. This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to Taligent may not be removed. | ||
| 35 | * Taligent is a registered trademark of Taligent, Inc. | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | package java.text; | ||
| 40 | |||
| 41 | import java.text.Normalizer; | ||
| 42 | import java.util.Vector; | ||
| 43 | import java.util.Locale; | ||
| 44 | |||
| 45 | /** | ||
| 46 | * The <code>RuleBasedCollator</code> class is a concrete subclass of | ||
| 47 | * <code>Collator</code> that provides a simple, data-driven, table | ||
| 48 | * collator. With this class you can create a customized table-based | ||
| 49 | * <code>Collator</code>. <code>RuleBasedCollator</code> maps | ||
| 50 | * characters to sort keys. | ||
| 51 | * | ||
| 52 | * <p> | ||
| 53 | * <code>RuleBasedCollator</code> has the following restrictions | ||
| 54 | * for efficiency (other subclasses may be used for more complex languages) : | ||
| 55 | * <ol> | ||
| 56 | * <li>If a special collation rule controlled by a <modifier> is | ||
| 57 | specified it applies to the whole collator object. | ||
| 58 | * <li>All non-mentioned characters are at the end of the | ||
| 59 | * collation order. | ||
| 60 | * </ol> | ||
| 61 | * | ||
| 62 | * <p> | ||
| 63 | * The collation table is composed of a list of collation rules, where each | ||
| 64 | * rule is of one of three forms: | ||
| 65 | * <pre> | ||
| 66 | * <modifier> | ||
| 67 | * <relation> <text-argument> | ||
| 68 | * <reset> <text-argument> | ||
| 69 | * </pre> | ||
| 70 | * The definitions of the rule elements is as follows: | ||
| 71 | * <UL Type=disc> | ||
| 72 | * <LI><strong>Text-Argument</strong>: A text-argument is any sequence of | ||
| 73 | * characters, excluding special characters (that is, common | ||
| 74 | * whitespace characters [0009-000D, 0020] and rule syntax characters | ||
| 75 | * [0021-002F, 003A-0040, 005B-0060, 007B-007E]). If those | ||
| 76 | * characters are desired, you can put them in single quotes | ||
| 77 | * (e.g. ampersand => '&'). Note that unquoted white space characters | ||
| 78 | * are ignored; e.g. <code>b c</code> is treated as <code>bc</code>. | ||
| 79 | * <LI><strong>Modifier</strong>: There are currently two modifiers that | ||
| 80 | * turn on special collation rules. | ||
| 81 | * <UL Type=square> | ||
| 82 | * <LI>'@' : Turns on backwards sorting of accents (secondary | ||
| 83 | * differences), as in French. | ||
| 84 | * <LI>'!' : Turns on Thai/Lao vowel-consonant swapping. If this | ||
| 85 | * rule is in force when a Thai vowel of the range | ||
| 86 | * \U0E40-\U0E44 precedes a Thai consonant of the range | ||
| 87 | * \U0E01-\U0E2E OR a Lao vowel of the range \U0EC0-\U0EC4 | ||
| 88 | * precedes a Lao consonant of the range \U0E81-\U0EAE then | ||
| 89 | * the vowel is placed after the consonant for collation | ||
| 90 | * purposes. | ||
| 91 | * </UL> | ||
| 92 | * <p>'@' : Indicates that accents are sorted backwards, as in French. | ||
| 93 | * <LI><strong>Relation</strong>: The relations are the following: | ||
| 94 | * <UL Type=square> | ||
| 95 | * <LI>'<' : Greater, as a letter difference (primary) | ||
| 96 | * <LI>';' : Greater, as an accent difference (secondary) | ||
| 97 | * <LI>',' : Greater, as a case difference (tertiary) | ||
| 98 | * <LI>'=' : Equal | ||
| 99 | * </UL> | ||
| 100 | * <LI><strong>Reset</strong>: There is a single reset | ||
| 101 | * which is used primarily for contractions and expansions, but which | ||
| 102 | * can also be used to add a modification at the end of a set of rules. | ||
| 103 | * <p>'&' : Indicates that the next rule follows the position to where | ||
| 104 | * the reset text-argument would be sorted. | ||
| 105 | * </UL> | ||
| 106 | * | ||
| 107 | * <p> | ||
| 108 | * This sounds more complicated than it is in practice. For example, the | ||
| 109 | * following are equivalent ways of expressing the same thing: | ||
| 110 | * <blockquote> | ||
| 111 | * <pre> | ||
| 112 | * a < b < c | ||
| 113 | * a < b & b < c | ||
| 114 | * a < c & a < b | ||
| 115 | * </pre> | ||
| 116 | * </blockquote> | ||
| 117 | * Notice that the order is important, as the subsequent item goes immediately | ||
| 118 | * after the text-argument. The following are not equivalent: | ||
| 119 | * <blockquote> | ||
| 120 | * <pre> | ||
| 121 | * a < b & a < c | ||
| 122 | * a < c & a < b | ||
| 123 | * </pre> | ||
| 124 | * </blockquote> | ||
| 125 | * Either the text-argument must already be present in the sequence, or some | ||
| 126 | * initial substring of the text-argument must be present. (e.g. "a < b & ae < | ||
| 127 | * e" is valid since "a" is present in the sequence before "ae" is reset). In | ||
| 128 | * this latter case, "ae" is not entered and treated as a single character; | ||
| 129 | * instead, "e" is sorted as if it were expanded to two characters: "a" | ||
| 130 | * followed by an "e". This difference appears in natural languages: in | ||
| 131 | * traditional Spanish "ch" is treated as though it contracts to a single | ||
| 132 | * character (expressed as "c < ch < d"), while in traditional German | ||
| 133 | * a-umlaut is treated as though it expanded to two characters | ||
| 134 | * (expressed as "a,A < b,B ... &ae;\u00e3&AE;\u00c3"). | ||
| 135 | * [\u00e3 and \u00c3 are, of course, the escape sequences for a-umlaut.] | ||
| 136 | * <p> | ||
| 137 | * <strong>Ignorable Characters</strong> | ||
| 138 | * <p> | ||
| 139 | * For ignorable characters, the first rule must start with a relation (the | ||
| 140 | * examples we have used above are really fragments; "a < b" really should be | ||
| 141 | * "< a < b"). If, however, the first relation is not "<", then all the all | ||
| 142 | * text-arguments up to the first "<" are ignorable. For example, ", - < a < b" | ||
| 143 | * makes "-" an ignorable character, as we saw earlier in the word | ||
| 144 | * "black-birds". In the samples for different languages, you see that most | ||
| 145 | * accents are ignorable. | ||
| 146 | * | ||
| 147 | * <p><strong>Normalization and Accents</strong> | ||
| 148 | * <p> | ||
| 149 | * <code>RuleBasedCollator</code> automatically processes its rule table to | ||
| 150 | * include both pre-composed and combining-character versions of | ||
| 151 | * accented characters. Even if the provided rule string contains only | ||
| 152 | * base characters and separate combining accent characters, the pre-composed | ||
| 153 | * accented characters matching all canonical combinations of characters from | ||
| 154 | * the rule string will be entered in the table. | ||
| 155 | * <p> | ||
| 156 | * This allows you to use a RuleBasedCollator to compare accented strings | ||
| 157 | * even when the collator is set to NO_DECOMPOSITION. There are two caveats, | ||
| 158 | * however. First, if the strings to be collated contain combining | ||
| 159 | * sequences that may not be in canonical order, you should set the collator to | ||
| 160 | * CANONICAL_DECOMPOSITION or FULL_DECOMPOSITION to enable sorting of | ||
| 161 | * combining sequences. Second, if the strings contain characters with | ||
| 162 | * compatibility decompositions (such as full-width and half-width forms), | ||
| 163 | * you must use FULL_DECOMPOSITION, since the rule tables only include | ||
| 164 | * canonical mappings. | ||
| 165 | * | ||
| 166 | * <p><strong>Errors</strong> | ||
| 167 | * <p> | ||
| 168 | * The following are errors: | ||
| 169 | * <UL Type=disc> | ||
| 170 | * <LI>A text-argument contains unquoted punctuation symbols | ||
| 171 | * (e.g. "a < b-c < d"). | ||
| 172 | * <LI>A relation or reset character not followed by a text-argument | ||
| 173 | * (e.g. "a < ,b"). | ||
| 174 | * <LI>A reset where the text-argument (or an initial substring of the | ||
| 175 | * text-argument) is not already in the sequence. | ||
| 176 | * (e.g. "a < b & e < f") | ||
| 177 | * </UL> | ||
| 178 | * If you produce one of these errors, a <code>RuleBasedCollator</code> throws | ||
| 179 | * a <code>ParseException</code>. | ||
| 180 | * | ||
| 181 | * <p><strong>Examples</strong> | ||
| 182 | * <p>Simple: "< a < b < c < d" | ||
| 183 | * <p>Norwegian: "< a, A < b, B < c, C < d, D < e, E < f, F | ||
| 184 | * < g, G < h, H < i, I < j, J < k, K < l, L | ||
| 185 | * < m, M < n, N < o, O < p, P < q, Q < r, R | ||
| 186 | * < s, S < t, T < u, U < v, V < w, W < x, X | ||
| 187 | * < y, Y < z, Z | ||
| 188 | * < \u00E6, \u00C6 | ||
| 189 | * < \u00F8, \u00D8 | ||
| 190 | * < \u00E5 = a\u030A, \u00C5 = A\u030A; | ||
| 191 | * aa, AA" | ||
| 192 | * | ||
| 193 | * <p> | ||
| 194 | * To create a <code>RuleBasedCollator</code> object with specialized | ||
| 195 | * rules tailored to your needs, you construct the <code>RuleBasedCollator</code> | ||
| 196 | * with the rules contained in a <code>String</code> object. For example: | ||
| 197 | * <blockquote> | ||
| 198 | * <pre> | ||
| 199 | * String simple = "< a< b< c< d"; | ||
| 200 | * RuleBasedCollator mySimple = new RuleBasedCollator(simple); | ||
| 201 | * </pre> | ||
| 202 | * </blockquote> | ||
| 203 | * Or: | ||
| 204 | * <blockquote> | ||
| 205 | * <pre> | ||
| 206 | * String Norwegian = "< a, A < b, B < c, C < d, D < e, E < f, F < g, G < h, H < i, I" + | ||
| 207 | * "< j, J < k, K < l, L < m, M < n, N < o, O < p, P < q, Q < r, R" + | ||
| 208 | * "< s, S < t, T < u, U < v, V < w, W < x, X < y, Y < z, Z" + | ||
| 209 | * "< \u00E6, \u00C6" + // Latin letter ae & AE | ||
| 210 | * "< \u00F8, \u00D8" + // Latin letter o & O with stroke | ||
| 211 | * "< \u00E5 = a\u030A," + // Latin letter a with ring above | ||
| 212 | * " \u00C5 = A\u030A;" + // Latin letter A with ring above | ||
| 213 | * " aa, AA"; | ||
| 214 | * RuleBasedCollator myNorwegian = new RuleBasedCollator(Norwegian); | ||
| 215 | * </pre> | ||
| 216 | * </blockquote> | ||
| 217 | * | ||
| 218 | * <p> | ||
| 219 | * A new collation rules string can be created by concatenating rules | ||
| 220 | * strings. For example, the rules returned by {@link #getRules()} could | ||
| 221 | * be concatenated to combine multiple <code>RuleBasedCollator</code>s. | ||
| 222 | * | ||
| 223 | * <p> | ||
| 224 | * The following example demonstrates how to change the order of | ||
| 225 | * non-spacing accents, | ||
| 226 | * <blockquote> | ||
| 227 | * <pre> | ||
| 228 | * // old rule | ||
| 229 | * String oldRules = "=\u0301;\u0300;\u0302;\u0308" // main accents | ||
| 230 | * + ";\u0327;\u0303;\u0304;\u0305" // main accents | ||
| 231 | * + ";\u0306;\u0307;\u0309;\u030A" // main accents | ||
| 232 | * + ";\u030B;\u030C;\u030D;\u030E" // main accents | ||
| 233 | * + ";\u030F;\u0310;\u0311;\u0312" // main accents | ||
| 234 | * + "< a , A ; ae, AE ; \u00e6 , \u00c6" | ||
| 235 | * + "< b , B < c, C < e, E & C < d, D"; | ||
| 236 | * // change the order of accent characters | ||
| 237 | * String addOn = "& \u0300 ; \u0308 ; \u0302"; | ||
| 238 | * RuleBasedCollator myCollator = new RuleBasedCollator(oldRules + addOn); | ||
| 239 | * </pre> | ||
| 240 | * </blockquote> | ||
| 241 | * | ||
| 242 | * @see Collator | ||
| 243 | * @see CollationElementIterator | ||
| 244 | * @author Helena Shih, Laura Werner, Richard Gillam | ||
| 245 | */ | ||
| 246 | public class RuleBasedCollator extends Collator{ | ||
| 247 | // IMPLEMENTATION NOTES: The implementation of the collation algorithm is | ||
| 248 | // divided across three classes: RuleBasedCollator, RBCollationTables, and | ||
| 249 | // CollationElementIterator. RuleBasedCollator contains the collator's | ||
| 250 | // transient state and includes the code that uses the other classes to | ||
| 251 | // implement comparison and sort-key building. RuleBasedCollator also | ||
| 252 | // contains the logic to handle French secondary accent sorting. | ||
| 253 | // A RuleBasedCollator has two CollationElementIterators. State doesn't | ||
| 254 | // need to be preserved in these objects between calls to compare() or | ||
| 255 | // getCollationKey(), but the objects persist anyway to avoid wasting extra | ||
| 256 | // creation time. compare() and getCollationKey() are synchronized to ensure | ||
| 257 | // thread safety with this scheme. The CollationElementIterator is responsible | ||
| 258 | // for generating collation elements from strings and returning one element at | ||
| 259 | // a time (sometimes there's a one-to-many or many-to-one mapping between | ||
| 260 | // characters and collation elements-- this class handles that). | ||
| 261 | // CollationElementIterator depends on RBCollationTables, which contains the | ||
| 262 | // collator's static state. RBCollationTables contains the actual data | ||
| 263 | // tables specifying the collation order of characters for a particular locale | ||
| 264 | // or use. It also contains the base logic that CollationElementIterator | ||
| 265 | // uses to map from characters to collation elements. A single RBCollationTables | ||
| 266 | // object is shared among all RuleBasedCollators for the same locale, and | ||
| 267 | // thus by all the CollationElementIterators they create. | ||
| 268 | |||
| 269 | /** | ||
| 270 | * RuleBasedCollator constructor. This takes the table rules and builds | ||
| 271 | * a collation table out of them. Please see RuleBasedCollator class | ||
| 272 | * description for more details on the collation rule syntax. | ||
| 273 | * @see java.util.Locale | ||
| 274 | * @param rules the collation rules to build the collation table from. | ||
| 275 | * @exception ParseException A format exception | ||
| 276 | * will be thrown if the build process of the rules fails. For | ||
| 277 | * example, build rule "a < ? < d" will cause the constructor to | ||
| 278 | * throw the ParseException because the '?' is not quoted. | ||
| 279 | */ | ||
| 280 | public RuleBasedCollator(String rules) throws ParseException { | ||
| 281 | this(rules, Collator.CANONICAL_DECOMPOSITION); | ||
482 more lines aren’t shown here.
Binary file not shown.
This diff is large: its 2351 lines aren’t shown here.
Binary file not shown.
This diff is large: its 280 lines aren’t shown here.
This diff is large: its 50 lines aren’t shown here.
This diff is large: its 66 lines aren’t shown here.
Binary file not shown.
This diff is large: its 108 lines aren’t shown here.
Binary file not shown.
This diff is large: its 62 lines aren’t shown here.
Binary file not shown.
This diff is large: its 115 lines aren’t shown here.
Binary file not shown.
This diff is large: its 63 lines aren’t shown here.
Binary file not shown.
This diff is large: its 63 lines aren’t shown here.
Binary file not shown.
This diff is large: its 114 lines aren’t shown here.
This diff is large: its 14 lines aren’t shown here.
This diff is large: its 51 lines aren’t shown here.