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.