diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
commit | adc854b798c1cfe3bfd4c27d68d5cee38ca617da (patch) | |
tree | 6aed8b4923ca428942cbaa7e848d50237a3d31e0 /text | |
parent | 1c0fed63c71ddb230f3b304aac12caffbedf2f21 (diff) | |
download | libcore-adc854b798c1cfe3bfd4c27d68d5cee38ca617da.zip libcore-adc854b798c1cfe3bfd4c27d68d5cee38ca617da.tar.gz libcore-adc854b798c1cfe3bfd4c27d68d5cee38ca617da.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'text')
62 files changed, 30943 insertions, 0 deletions
diff --git a/text/MODULE_LICENSE_APACHE2 b/text/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/text/MODULE_LICENSE_APACHE2 diff --git a/text/src/main/java/java/text/Annotation.java b/text/src/main/java/java/text/Annotation.java new file mode 100644 index 0000000..a45ccaa --- /dev/null +++ b/text/src/main/java/java/text/Annotation.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.text; + +/** + * Wrapper for a text attribute value which represents an annotation. An + * annotation has two special aspects: + * <ol> + * <li>it is connected to a range of main text; if this range or the main text + * is changed then the annotation becomes invalid,</li> + * <li>it can not be joined with adjacent annotations even if the text attribute + * value is the same.</li> + * </ol> + * <p> + * By wrapping text attribute values into an {@code Annotation}, these aspects + * will be taken into account when handling annotation text and the + * corresponding main text. + * </p> + * <p> + * Note: There is no semantic connection between this annotation class and the + * {@code java.lang.annotation} package. + * </p> + * + * @since Android 1.0 + */ +public class Annotation { + + private Object value; + + /** + * Constructs a new {@code Annotation}. + * + * @param attribute the attribute attached to this annotation. This may be + * {@code null}. + * @since Android 1.0 + */ + public Annotation(Object attribute) { + value = attribute; + } + + /** + * Returns the value of this annotation. The value may be {@code null}. + * + * @return the value of this annotation or {@code null}. + * @since Android 1.0 + */ + public Object getValue() { + return value; + } + + /** + * Returns this annotation in string representation. + * + * @return the string representation of this annotation. + * @since Android 1.0 + */ + @Override + public String toString() { + return getClass().getName() + "[value=" + value + ']'; //$NON-NLS-1$ + } +} diff --git a/text/src/main/java/java/text/AttributedCharacterIterator.java b/text/src/main/java/java/text/AttributedCharacterIterator.java new file mode 100644 index 0000000..07bbdec --- /dev/null +++ b/text/src/main/java/java/text/AttributedCharacterIterator.java @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.text; + +import java.io.InvalidObjectException; +import java.io.Serializable; +import java.util.Map; +import java.util.Set; + +import org.apache.harmony.text.internal.nls.Messages; + +/** + * Extends the + * {@link CharacterIterator} interface, adding support for iterating over + * attributes and not only characters. An + * {@code AttributedCharacterIterator} also allows the user to find runs and + * their limits. Runs are defined as ranges of characters that all have the same + * attributes with the same values. + * + * @since Android 1.0 + */ +public interface AttributedCharacterIterator extends CharacterIterator { + + /** + * Defines keys for text attributes. + * + * @since Android 1.0 + */ + public static class Attribute implements Serializable { + + private static final long serialVersionUID = -9142742483513960612L; + + /** + * This attribute marks segments from an input method. Most input + * methods create these segments for words. + * + * The value objects are of the type {@code Annotation} which contain + * {@code null}. + * + * @since Android 1.0 + */ + public static final Attribute INPUT_METHOD_SEGMENT = new Attribute( + "input_method_segment"); //$NON-NLS-1$ + + /** + * The attribute describing the language of a character. The value + * objects are of type {@code Locale} or a subtype of it. + * + * @since Android 1.0 + */ + public static final Attribute LANGUAGE = new Attribute("language"); //$NON-NLS-1$ + + /** + * For languages that have different reading directions of text (like + * Japanese), this attribute allows to define which reading should be + * used. The value objects are of type {@code Annotation} which + * contain a {@code String}. + * + * @since Android 1.0 + */ + public static final Attribute READING = new Attribute("reading"); //$NON-NLS-1$ + + private String name; + + /** + * The constructor for an {@code Attribute} with the name passed. + * + * @param name + * the name of the new {@code Attribute}. + * @since Android 1.0 + */ + protected Attribute(String name) { + this.name = name; + } + + /** + * Compares this attribute with the specified object. Checks if both + * objects are the same instance. It is defined final so all subclasses + * have the same behavior for this method. + * + * @param object + * the object to compare against. + * @return {@code true} if the object passed is equal to this instance; + * {@code false} otherwise. + * @since Android 1.0 + */ + @Override + public final boolean equals(Object object) { + return super.equals(object); + } + + /** + * Returns the name of this attribute. + * + * @return the name of this attribute. + * @since Android 1.0 + */ + protected String getName() { + return name; + } + + /** + * Calculates the hash code for objects of type {@code Attribute}. It + * is defined final so all sub types calculate their hash code + * identically. + * + * @return the hash code for this instance of {@code Attribute}. + * @since Android 1.0 + */ + @Override + public final int hashCode() { + return super.hashCode(); + } + + /** + * Resolves a deserialized instance to the correct constant attribute. + * + * @return the {@code Attribute} this instance represents. + * @throws InvalidObjectException + * if this instance is not of type {@code Attribute.class} + * or if it is not a known {@code Attribute}. + * @since Android 1.0 + */ + protected Object readResolve() throws InvalidObjectException { + if (this.getClass() != Attribute.class) { + // text.0C=cannot resolve subclasses + throw new InvalidObjectException(Messages.getString("text.0C")); //$NON-NLS-1$ + } + if (this.name.equals(INPUT_METHOD_SEGMENT.name)) { + return INPUT_METHOD_SEGMENT; + } + if (this.name.equals(LANGUAGE.name)) { + return LANGUAGE; + } + if (this.name.equals(READING.name)) { + return READING; + } + // text.02=Unknown attribute + throw new InvalidObjectException(Messages.getString("text.02")); //$NON-NLS-1$ + } + + /** + * Returns the name of the class followed by a "(", the name of the + * attribute, and a ")". + * + * @return the string representing this instance. + * @since Android 1.0 + */ + @Override + public String toString() { + return getClass().getName() + '(' + getName() + ')'; + } + } + + /** + * Returns a set of attributes present in the {@code + * AttributedCharacterIterator}. An empty set is returned if no attributes + * were defined. + * + * @return a set of attribute keys; may be empty. + * @since Android 1.0 + */ + public Set<Attribute> getAllAttributeKeys(); + + /** + * Returns the value stored in the attribute for the current character. If + * the attribute was not defined then {@code null} is returned. + * + * @param attribute the attribute for which the value should be returned. + * @return the value of the requested attribute for the current character or + * {@code null} if it was not defined. + * @since Android 1.0 + */ + public Object getAttribute(Attribute attribute); + + /** + * Returns a map of all attributes of the current character. If no + * attributes were defined for the current character then an empty map is + * returned. + * + * @return a map of all attributes for the current character or an empty + * map. + * @since Android 1.0 + */ + public Map<Attribute, Object> getAttributes(); + + /** + * Returns the index of the last character in the run having the same + * attributes as the current character. + * + * @return the index of the last character of the current run. + * @since Android 1.0 + */ + public int getRunLimit(); + + /** + * Returns the index of the last character in the run that has the same + * attribute value for the given attribute as the current character. + * + * @param attribute + * the attribute which the run is based on. + * @return the index of the last character of the current run. + * @since Android 1.0 + */ + public int getRunLimit(Attribute attribute); + + /** + * Returns the index of the last character in the run that has the same + * attribute values for the attributes in the set as the current character. + * + * @param attributes + * the set of attributes which the run is based on. + * @return the index of the last character of the current run. + * @since Android 1.0 + */ + public int getRunLimit(Set<? extends Attribute> attributes); + + /** + * Returns the index of the first character in the run that has the same + * attributes as the current character. + * + * @return the index of the last character of the current run. + * @since Android 1.0 + */ + public int getRunStart(); + + /** + * Returns the index of the first character in the run that has the same + * attribute value for the given attribute as the current character. + * + * @param attribute + * the attribute which the run is based on. + * @return the index of the last character of the current run. + * @since Android 1.0 + */ + public int getRunStart(Attribute attribute); + + /** + * Returns the index of the first character in the run that has the same + * attribute values for the attributes in the set as the current character. + * + * @param attributes + * the set of attributes which the run is based on. + * @return the index of the last character of the current run. + * @since Android 1.0 + */ + public int getRunStart(Set<? extends Attribute> attributes); +} diff --git a/text/src/main/java/java/text/AttributedString.java b/text/src/main/java/java/text/AttributedString.java new file mode 100644 index 0000000..540e671 --- /dev/null +++ b/text/src/main/java/java/text/AttributedString.java @@ -0,0 +1,792 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package java.text; + +import java.text.AttributedCharacterIterator.Attribute; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import org.apache.harmony.text.internal.nls.Messages; + +/** + * Holds a string with attributes describing the characters of + * this string. + * + * @since Android 1.0 + */ +public class AttributedString { + + String text; + + Map<AttributedCharacterIterator.Attribute, List<Range>> attributeMap; + + static class Range { + int start; + + int end; + + Object value; + + Range(int s, int e, Object v) { + start = s; + end = e; + value = v; + } + } + + static class AttributedIterator implements AttributedCharacterIterator { + + private int begin, end, offset; + + private AttributedString attrString; + + private HashSet<Attribute> attributesAllowed; + + AttributedIterator(AttributedString attrString) { + this.attrString = attrString; + begin = 0; + end = attrString.text.length(); + offset = 0; + } + + AttributedIterator(AttributedString attrString, + AttributedCharacterIterator.Attribute[] attributes, int begin, + int end) { + if (begin < 0 || end > attrString.text.length() || begin > end) { + throw new IllegalArgumentException(); + } + this.begin = begin; + this.end = end; + offset = begin; + this.attrString = attrString; + if (attributes != null) { + HashSet<Attribute> set = new HashSet<Attribute>( + (attributes.length * 4 / 3) + 1); + for (int i = attributes.length; --i >= 0;) { + set.add(attributes[i]); + } + attributesAllowed = set; + } + } + + /** + * Returns a new {@code AttributedIterator} with the same source string, + * begin, end, and current index as this attributed iterator. + * + * @return a shallow copy of this attributed iterator. + * @see java.lang.Cloneable + */ + @Override + @SuppressWarnings("unchecked") + public Object clone() { + try { + AttributedIterator clone = (AttributedIterator) super.clone(); + if (attributesAllowed != null) { + clone.attributesAllowed = (HashSet<Attribute>) attributesAllowed + .clone(); + } + return clone; + } catch (CloneNotSupportedException e) { + return null; + } + } + + public char current() { + if (offset == end) { + return DONE; + } + return attrString.text.charAt(offset); + } + + public char first() { + if (begin == end) { + return DONE; + } + offset = begin; + return attrString.text.charAt(offset); + } + + /** + * Returns the begin index in the source string. + * + * @return the index of the first character to iterate. + */ + public int getBeginIndex() { + return begin; + } + + /** + * Returns the end index in the source String. + * + * @return the index one past the last character to iterate. + */ + public int getEndIndex() { + return end; + } + + /** + * Returns the current index in the source String. + * + * @return the current index. + */ + public int getIndex() { + return offset; + } + + private boolean inRange(Range range) { + if (!(range.value instanceof Annotation)) { + return true; + } + return range.start >= begin && range.start < end + && range.end > begin && range.end <= end; + } + + private boolean inRange(List<Range> ranges) { + Iterator<Range> it = ranges.iterator(); + while (it.hasNext()) { + Range range = it.next(); + if (range.start >= begin && range.start < end) { + return !(range.value instanceof Annotation) + || (range.end > begin && range.end <= end); + } else if (range.end > begin && range.end <= end) { + return !(range.value instanceof Annotation) + || (range.start >= begin && range.start < end); + } + } + return false; + } + + /** + * Returns a set of attributes present in the {@code AttributedString}. + * An empty set returned indicates that no attributes where defined + * + * @return a set of attribute keys that may be empty. + */ + public Set<AttributedIterator.Attribute> getAllAttributeKeys() { + if (begin == 0 && end == attrString.text.length() + && attributesAllowed == null) { + return attrString.attributeMap.keySet(); + } + + Set<AttributedIterator.Attribute> result = new HashSet<Attribute>( + (attrString.attributeMap.size() * 4 / 3) + 1); + Iterator<Map.Entry<Attribute, List<Range>>> it = attrString.attributeMap + .entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<Attribute, List<Range>> entry = it.next(); + if (attributesAllowed == null + || attributesAllowed.contains(entry.getKey())) { + List<Range> ranges = entry.getValue(); + if (inRange(ranges)) { + result.add(entry.getKey()); + } + } + } + return result; + } + + private Object currentValue(List<Range> ranges) { + Iterator<Range> it = ranges.iterator(); + while (it.hasNext()) { + Range range = it.next(); + if (offset >= range.start && offset < range.end) { + return inRange(range) ? range.value : null; + } + } + return null; + } + + public Object getAttribute( + AttributedCharacterIterator.Attribute attribute) { + if (attributesAllowed != null + && !attributesAllowed.contains(attribute)) { + return null; + } + ArrayList<Range> ranges = (ArrayList<Range>) attrString.attributeMap + .get(attribute); + if (ranges == null) { + return null; + } + return currentValue(ranges); + } + + public Map<Attribute, Object> getAttributes() { + Map<Attribute, Object> result = new HashMap<Attribute, Object>( + (attrString.attributeMap.size() * 4 / 3) + 1); + Iterator<Map.Entry<Attribute, List<Range>>> it = attrString.attributeMap + .entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<Attribute, List<Range>> entry = it.next(); + if (attributesAllowed == null + || attributesAllowed.contains(entry.getKey())) { + Object value = currentValue(entry.getValue()); + if (value != null) { + result.put(entry.getKey(), value); + } + } + } + return result; + } + + public int getRunLimit() { + return getRunLimit(getAllAttributeKeys()); + } + + private int runLimit(List<Range> ranges) { + int result = end; + ListIterator<Range> it = ranges.listIterator(ranges.size()); + while (it.hasPrevious()) { + Range range = it.previous(); + if (range.end <= begin) { + break; + } + if (offset >= range.start && offset < range.end) { + return inRange(range) ? range.end : result; + } else if (offset >= range.end) { + break; + } + result = range.start; + } + return result; + } + + public int getRunLimit(AttributedCharacterIterator.Attribute attribute) { + if (attributesAllowed != null + && !attributesAllowed.contains(attribute)) { + return end; + } + ArrayList<Range> ranges = (ArrayList<Range>) attrString.attributeMap + .get(attribute); + if (ranges == null) { + return end; + } + return runLimit(ranges); + } + + public int getRunLimit(Set<? extends Attribute> attributes) { + int limit = end; + Iterator<? extends Attribute> it = attributes.iterator(); + while (it.hasNext()) { + AttributedCharacterIterator.Attribute attribute = it.next(); + int newLimit = getRunLimit(attribute); + if (newLimit < limit) { + limit = newLimit; + } + } + return limit; + } + + public int getRunStart() { + return getRunStart(getAllAttributeKeys()); + } + + private int runStart(List<Range> ranges) { + int result = begin; + Iterator<Range> it = ranges.iterator(); + while (it.hasNext()) { + Range range = it.next(); + if (range.start >= end) { + break; + } + if (offset >= range.start && offset < range.end) { + return inRange(range) ? range.start : result; + } else if (offset < range.start) { + break; + } + result = range.end; + } + return result; + } + + public int getRunStart(AttributedCharacterIterator.Attribute attribute) { + if (attributesAllowed != null + && !attributesAllowed.contains(attribute)) { + return begin; + } + ArrayList<Range> ranges = (ArrayList<Range>) attrString.attributeMap + .get(attribute); + if (ranges == null) { + return begin; + } + return runStart(ranges); + } + + public int getRunStart(Set<? extends Attribute> attributes) { + int start = begin; + Iterator<? extends Attribute> it = attributes.iterator(); + while (it.hasNext()) { + AttributedCharacterIterator.Attribute attribute = it.next(); + int newStart = getRunStart(attribute); + if (newStart > start) { + start = newStart; + } + } + return start; + } + + public char last() { + if (begin == end) { + return DONE; + } + offset = end - 1; + return attrString.text.charAt(offset); + } + + public char next() { + if (offset >= (end - 1)) { + offset = end; + return DONE; + } + return attrString.text.charAt(++offset); + } + + public char previous() { + if (offset == begin) { + return DONE; + } + return attrString.text.charAt(--offset); + } + + public char setIndex(int location) { + if (location < begin || location > end) { + throw new IllegalArgumentException(); + } + offset = location; + if (offset == end) { + return DONE; + } + return attrString.text.charAt(offset); + } + } + + /** + * Constructs an {@code AttributedString} from an {@code + * AttributedCharacterIterator}, which represents attributed text. + * + * @param iterator + * the {@code AttributedCharacterIterator} that contains the text + * for this attributed string. + * @since Android 1.0 + */ + public AttributedString(AttributedCharacterIterator iterator) { + if (iterator.getBeginIndex() > iterator.getEndIndex()) { + // text.0A=Invalid substring range + throw new IllegalArgumentException(Messages.getString("text.0A")); //$NON-NLS-1$ + } + StringBuffer buffer = new StringBuffer(); + for (int i = iterator.getBeginIndex(); i < iterator.getEndIndex(); i++) { + buffer.append(iterator.current()); + iterator.next(); + } + text = buffer.toString(); + Set<AttributedCharacterIterator.Attribute> attributes = iterator + .getAllAttributeKeys(); + if (attributes == null) { + return; + } + attributeMap = new HashMap<Attribute, List<Range>>( + (attributes.size() * 4 / 3) + 1); + + Iterator<Attribute> it = attributes.iterator(); + while (it.hasNext()) { + AttributedCharacterIterator.Attribute attribute = it.next(); + iterator.setIndex(0); + while (iterator.current() != CharacterIterator.DONE) { + int start = iterator.getRunStart(attribute); + int limit = iterator.getRunLimit(attribute); + Object value = iterator.getAttribute(attribute); + if (value != null) { + addAttribute(attribute, value, start, limit); + } + iterator.setIndex(limit); + } + } + } + + private AttributedString(AttributedCharacterIterator iterator, int start, + int end, Set<Attribute> attributes) { + if (start < iterator.getBeginIndex() || end > iterator.getEndIndex() + || start > end) { + throw new IllegalArgumentException(); + } + + if (attributes == null) { + return; + } + + StringBuffer buffer = new StringBuffer(); + iterator.setIndex(start); + while (iterator.getIndex() < end) { + buffer.append(iterator.current()); + iterator.next(); + } + text = buffer.toString(); + attributeMap = new HashMap<Attribute, List<Range>>( + (attributes.size() * 4 / 3) + 1); + + Iterator<Attribute> it = attributes.iterator(); + while (it.hasNext()) { + AttributedCharacterIterator.Attribute attribute = it.next(); + iterator.setIndex(start); + while (iterator.getIndex() < end) { + Object value = iterator.getAttribute(attribute); + int runStart = iterator.getRunStart(attribute); + int limit = iterator.getRunLimit(attribute); + if ((value instanceof Annotation && runStart >= start && limit <= end) + || (value != null && !(value instanceof Annotation))) { + addAttribute(attribute, value, (runStart < start ? start + : runStart) + - start, (limit > end ? end : limit) - start); + } + iterator.setIndex(limit); + } + } + } + + /** + * Constructs an {@code AttributedString} from a range of the text contained + * in the specified {@code AttributedCharacterIterator}, starting at {@code + * start} and ending at {@code end}. All attributes will be copied to this + * attributed string. + * + * @param iterator + * the {@code AttributedCharacterIterator} that contains the text + * for this attributed string. + * @param start + * the start index of the range of the copied text. + * @param end + * the end index of the range of the copied text. + * @throws IllegalArgumentException + * if {@code start} is less than first index of + * {@code iterator}, {@code end} is greater than the last + * index + 1 in {@code iterator} or if {@code start > end}. + * @since Android 1.0 + */ + public AttributedString(AttributedCharacterIterator iterator, int start, + int end) { + this(iterator, start, end, iterator.getAllAttributeKeys()); + } + + /** + * Constructs an {@code AttributedString} from a range of the text contained + * in the specified {@code AttributedCharacterIterator}, starting at {@code + * start}, ending at {@code end} and it will copy the attributes defined in + * the specified set. If the set is {@code null} then all attributes are + * copied. + * + * @param iterator + * the {@code AttributedCharacterIterator} that contains the text + * for this attributed string. + * @param start + * the start index of the range of the copied text. + * @param end + * the end index of the range of the copied text. + * @param attributes + * the set of attributes that will be copied, or all if it is + * {@code null}. + * @throws IllegalArgumentException + * if {@code start} is less than first index of + * {@code iterator}, {@code end} is greater than the last index + + * 1 in {@code iterator} or if {@code start > end}. + * @since Android 1.0 + */ + public AttributedString(AttributedCharacterIterator iterator, int start, + int end, AttributedCharacterIterator.Attribute[] attributes) { + // BEGIN android-removed + // this(iterator, start, end, new HashSet<Attribute>(Arrays + // .asList(attributes))); + // END android-removed + // BEGIN android-added + this(iterator, start, end, (attributes == null + ? new HashSet<Attribute>() + : new HashSet<Attribute>(Arrays.asList(attributes)))); + // END android-added + } + + /** + * Creates an {@code AttributedString} from the given text. + * + * @param value + * the text to take as base for this attributed string. + * @since Android 1.0 + */ + public AttributedString(String value) { + if (value == null) { + throw new NullPointerException(); + } + text = value; + attributeMap = new HashMap<Attribute, List<Range>>(11); + } + + /** + * Creates an {@code AttributedString} from the given text and the + * attributes. The whole text has the given attributes applied. + * + * @param value + * the text to take as base for this attributed string. + * @param attributes + * the attributes that the text is associated with. + * @throws IllegalArgumentException + * if the length of {@code value} is 0 but the size of {@code + * attributes} is greater than 0. + * @throws NullPointerException + * if {@code value} is {@code null}. + * @since Android 1.0 + */ + public AttributedString(String value, + Map<? extends AttributedCharacterIterator.Attribute, ?> attributes) { + if (value == null) { + throw new NullPointerException(); + } + if (value.length() == 0 && !attributes.isEmpty()) { + // text.0B=Cannot add attributes to empty string + throw new IllegalArgumentException(Messages.getString("text.0B")); //$NON-NLS-1$ + } + text = value; + attributeMap = new HashMap<Attribute, List<Range>>( + (attributes.size() * 4 / 3) + 1); + Iterator<?> it = attributes.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next(); + ArrayList<Range> ranges = new ArrayList<Range>(1); + ranges.add(new Range(0, text.length(), entry.getValue())); + attributeMap.put((AttributedCharacterIterator.Attribute) entry + .getKey(), ranges); + } + } + + /** + * Applies a given attribute to this string. + * + * @param attribute + * the attribute that will be applied to this string. + * @param value + * the value of the attribute that will be applied to this + * string. + * @throws IllegalArgumentException + * if the length of this attributed string is 0. + * @throws NullPointerException + * if {@code attribute} is {@code null}. + * @since Android 1.0 + */ + public void addAttribute(AttributedCharacterIterator.Attribute attribute, + Object value) { + if (null == attribute) { + throw new NullPointerException(); + } + if (text.length() == 0) { + throw new IllegalArgumentException(); + } + + List<Range> ranges = attributeMap.get(attribute); + if (ranges == null) { + ranges = new ArrayList<Range>(1); + attributeMap.put(attribute, ranges); + } else { + ranges.clear(); + } + ranges.add(new Range(0, text.length(), value)); + } + + /** + * Applies a given attribute to the given range of this string. + * + * @param attribute + * the attribute that will be applied to this string. + * @param value + * the value of the attribute that will be applied to this + * string. + * @param start + * the start of the range where the attribute will be applied. + * @param end + * the end of the range where the attribute will be applied. + * @throws IllegalArgumentException + * if {@code start < 0}, {@code end} is greater than the length + * of this string, or if {@code start >= end}. + * @throws NullPointerException + * if {@code attribute} is {@code null}. + * @since Android 1.0 + */ + public void addAttribute(AttributedCharacterIterator.Attribute attribute, + Object value, int start, int end) { + if (null == attribute) { + throw new NullPointerException(); + } + if (start < 0 || end > text.length() || start >= end) { + throw new IllegalArgumentException(); + } + + if (value == null) { + return; + } + + List<Range> ranges = attributeMap.get(attribute); + if (ranges == null) { + ranges = new ArrayList<Range>(1); + ranges.add(new Range(start, end, value)); + attributeMap.put(attribute, ranges); + return; + } + ListIterator<Range> it = ranges.listIterator(); + // BEGIN android-changed + // copied from a newer version of harmony + // value can't be null + while (it.hasNext()) { + Range range = it.next(); + if (end <= range.start) { + it.previous(); + break; + } else if (start < range.end + || (start == range.end && value.equals(range.value))) { + Range r1 = null, r3; + it.remove(); + r1 = new Range(range.start, start, range.value); + r3 = new Range(end, range.end, range.value); + + while (end > range.end && it.hasNext()) { + range = it.next(); + if (end <= range.end) { + if (end > range.start + || (end == range.start && value.equals(range.value))) { + it.remove(); + r3 = new Range(end, range.end, range.value); + break; + } + } else { + it.remove(); + } + } + + if (value.equals(r1.value)) { + if (value.equals(r3.value)) { + it.add(new Range(r1.start < start ? r1.start : start, + r3.end > end ? r3.end : end, r1.value)); + } else { + it.add(new Range(r1.start < start ? r1.start : start, + end, r1.value)); + if (r3.start < r3.end) { + it.add(r3); + } + } + } else { + if (value.equals(r3.value)) { + if (r1.start < r1.end) { + it.add(r1); + } + it.add(new Range(start, r3.end > end ? r3.end : end, + r3.value)); + } else { + if (r1.start < r1.end) { + it.add(r1); + } + it.add(new Range(start, end, value)); + if (r3.start < r3.end) { + it.add(r3); + } + } + } + return; + } + } + // END android-changed + it.add(new Range(start, end, value)); + } + + /** + * Applies a given set of attributes to the given range of the string. + * + * @param attributes + * the set of attributes that will be applied to this string. + * @param start + * the start of the range where the attribute will be applied. + * @param end + * the end of the range where the attribute will be applied. + * @throws IllegalArgumentException + * if {@code start < 0}, {@code end} is greater than the length + * of this string, or if {@code start >= end}. + * @since Android 1.0 + */ + public void addAttributes( + Map<? extends AttributedCharacterIterator.Attribute, ?> attributes, + int start, int end) { + Iterator<?> it = attributes.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next(); + addAttribute( + (AttributedCharacterIterator.Attribute) entry.getKey(), + entry.getValue(), start, end); + } + } + + /** + * Returns an {@code AttributedCharacterIterator} that gives access to the + * complete content of this attributed string. + * + * @return the newly created {@code AttributedCharacterIterator}. + * @since Android 1.0 + */ + public AttributedCharacterIterator getIterator() { + return new AttributedIterator(this); + } + + /** + * Returns an {@code AttributedCharacterIterator} that gives access to the + * complete content of this attributed string. Only attributes contained in + * {@code attributes} are available from this iterator if they are defined + * for this text. + * + * @param attributes + * the array containing attributes that will be in the new + * iterator if they are defined for this text. + * @return the newly created {@code AttributedCharacterIterator}. + * @since Android 1.0 + */ + public AttributedCharacterIterator getIterator( + AttributedCharacterIterator.Attribute[] attributes) { + return new AttributedIterator(this, attributes, 0, text.length()); + } + + /** + * Returns an {@code AttributedCharacterIterator} that gives access to the + * contents of this attributed string starting at index {@code start} up to + * index {@code end}. Only attributes contained in {@code attributes} are + * available from this iterator if they are defined for this text. + * + * @param attributes + * the array containing attributes that will be in the new + * iterator if they are defined for this text. + * @param start + * the start index of the iterator on the underlying text. + * @param end + * the end index of the iterator on the underlying text. + * @return the newly created {@code AttributedCharacterIterator}. + * @since Android 1.0 + */ + public AttributedCharacterIterator getIterator( + AttributedCharacterIterator.Attribute[] attributes, int start, + int end) { + return new AttributedIterator(this, attributes, start, end); + } +} diff --git a/text/src/main/java/java/text/Bidi.java b/text/src/main/java/java/text/Bidi.java new file mode 100644 index 0000000..228dab3 --- /dev/null +++ b/text/src/main/java/java/text/Bidi.java @@ -0,0 +1,595 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.text; + +import java.awt.font.NumericShaper; +import java.awt.font.TextAttribute; +import java.util.Arrays; +import java.util.LinkedList; + +import org.apache.harmony.text.BidiRun; +import org.apache.harmony.text.BidiWrapper; +import org.apache.harmony.text.internal.nls.Messages; + +/** + * Provides the Unicode Bidirectional Algorithm. The algorithm is + * defined in the Unicode Standard Annex #9, version 13, also described in The + * Unicode Standard, Version 4.0 . + * + * Use a {@code Bidi} object to get the information on the position reordering of a + * bidirectional text, such as Arabic or Hebrew. The natural display ordering of + * horizontal text in these languages is from right to left, while they order + * numbers from left to right. + * + * If the text contains multiple runs, the information of each run can be + * obtained from the run index. The level of any particular run indicates the + * direction of the text as well as the nesting level. Left-to-right runs have + * even levels while right-to-left runs have odd levels. + * + * @since Android 1.0 + */ +public final class Bidi { + /** + * Constant that indicates the default base level. If there is no strong + * character, then set the paragraph level to 0 (left-to-right). + * + * @since Android 1.0 + */ + public static final int DIRECTION_DEFAULT_LEFT_TO_RIGHT = -2; + + /** + * Constant that indicates the default base level. If there is no strong + * character, then set the paragraph level to 1 (right-to-left). + * + * @since Android 1.0 + */ + public static final int DIRECTION_DEFAULT_RIGHT_TO_LEFT = -1; + + /** + * Constant that specifies the default base level as 0 (left-to-right). + * + * @since Android 1.0 + */ + public static final int DIRECTION_LEFT_TO_RIGHT = 0; + + /** + * Constant that specifies the default base level as 1 (right-to-left). + * + * @since Android 1.0 + */ + public static final int DIRECTION_RIGHT_TO_LEFT = 1; + + /** + * Creates a {@code Bidi} object from the {@code + * AttributedCharacterIterator} of a paragraph text. The RUN_DIRECTION + * attribute determines the base direction of the bidirectional text. If it + * is not specified explicitly, the algorithm uses + * DIRECTION_DEFAULT_LEFT_TO_RIGHT by default. The BIDI_EMBEDDING attribute + * specifies the level of embedding for each character. Values between -1 + * and -62 denote overrides at the level's absolute value, values from 1 to + * 62 indicate embeddings, and the 0 value indicates the level is calculated + * by the algorithm automatically. For the character with no BIDI_EMBEDDING + * attribute or with a improper attribute value, such as a {@code null} + * value, the algorithm treats its embedding level as 0. The NUMERIC_SHAPING + * attribute specifies the instance of NumericShaper used to convert + * European digits to other decimal digits before performing the bidi + * algorithm. + * + * @param paragraph + * the String containing the paragraph text to perform the + * algorithm. + * @throws IllegalArgumentException + * if {@code paragraph} is {@code null}. + * @see TextAttribute#BIDI_EMBEDDING + * @see TextAttribute#NUMERIC_SHAPING + * @see TextAttribute#RUN_DIRECTION + * @since Android 1.0 + */ + public Bidi(AttributedCharacterIterator paragraph) { + if (paragraph == null) { + // text.14=paragraph is null + throw new IllegalArgumentException(Messages.getString("text.14")); //$NON-NLS-1$ + } + + int begin = paragraph.getBeginIndex(); + int end = paragraph.getEndIndex(); + int length = end - begin; + char text[] = new char[length + 1]; // One more char for + // AttributedCharacterIterator.DONE + + if (length != 0) { + text[0] = paragraph.first(); + } else { + paragraph.first(); + } + + // First check the RUN_DIRECTION attribute. + int flags = DIRECTION_DEFAULT_LEFT_TO_RIGHT; + Object direction = paragraph.getAttribute(TextAttribute.RUN_DIRECTION); + if (direction != null && direction instanceof Boolean) { + if (direction.equals(TextAttribute.RUN_DIRECTION_LTR)) { + flags = DIRECTION_LEFT_TO_RIGHT; + } else { + flags = DIRECTION_RIGHT_TO_LEFT; + } + } + + // Retrieve the text and gather BIDI_EMBEDDINGS + byte embeddings[] = null; + for (int textLimit = 1, i = 1; i < length; textLimit = paragraph + .getRunLimit(TextAttribute.BIDI_EMBEDDING) + - begin + 1) { + Object embedding = paragraph + .getAttribute(TextAttribute.BIDI_EMBEDDING); + if (embedding != null && embedding instanceof Integer) { + int embLevel = ((Integer) embedding).intValue(); + + if (embeddings == null) { + embeddings = new byte[length]; + } + + for (; i < textLimit; i++) { + text[i] = paragraph.next(); + embeddings[i - 1] = (byte) embLevel; + } + } else { + for (; i < textLimit; i++) { + text[i] = paragraph.next(); + } + } + } + + // Apply NumericShaper to the text + Object numericShaper = paragraph + .getAttribute(TextAttribute.NUMERIC_SHAPING); + if (numericShaper != null && numericShaper instanceof NumericShaper) { + ((NumericShaper) numericShaper).shape(text, 0, length); + } + + long pBidi = createUBiDi(text, 0, embeddings, 0, length, flags); + readBidiInfo(pBidi); + BidiWrapper.ubidi_close(pBidi); + } + + /** + * Creates a {@code Bidi} object. + * + * @param text + * the char array of the paragraph text that is processed. + * @param textStart + * the index in {@code text} of the start of the paragraph. + * @param embeddings + * the embedding level array of the paragraph text, specifying + * the embedding level information for each character. Values + * between -1 and -61 denote overrides at the level's absolute + * value, values from 1 to 61 indicate embeddings, and the 0 + * value indicates the level is calculated by the algorithm + * automatically. + * @param embStart + * the index in {@code embeddings} of the start of the paragraph. + * @param paragraphLength + * the length of the text to perform the algorithm. + * @param flags + * indicates the base direction of the bidirectional text. It is + * expected that this will be one of the direction constant + * values defined in this class. An unknown value is treated as + * DIRECTION_DEFAULT_LEFT_TO_RIGHT. + * @throws IllegalArgumentException + * if {@code textStart}, {@code embStart}, or {@code + * paragraphLength} is negative; if + * {@code text.length < textStart + paragraphLength} or + * {@code embeddings.length < embStart + paragraphLength}. + * @see #DIRECTION_LEFT_TO_RIGHT + * @see #DIRECTION_RIGHT_TO_LEFT + * @see #DIRECTION_DEFAULT_RIGHT_TO_LEFT + * @see #DIRECTION_DEFAULT_LEFT_TO_RIGHT + * @since Android 1.0 + */ + public Bidi(char[] text, int textStart, byte[] embeddings, int embStart, + int paragraphLength, int flags) { + if (textStart < 0) { + // text.0D=Negative textStart value {0} + throw new IllegalArgumentException(Messages.getString( + "text.0D", textStart)); //$NON-NLS-1$ + } + if (embStart < 0) { + // text.10=Negative embStart value {0} + throw new IllegalArgumentException(Messages.getString( + "text.10", embStart)); //$NON-NLS-1$ + } + if (paragraphLength < 0) { + // text.11=Negative paragraph length {0} + throw new IllegalArgumentException(Messages.getString( + "text.11", paragraphLength)); //$NON-NLS-1$ + } + long pBidi = createUBiDi(text, textStart, embeddings, embStart, + paragraphLength, flags); + readBidiInfo(pBidi); + BidiWrapper.ubidi_close(pBidi); + } + + /** + * Creates a {@code Bidi} object. + * + * @param paragraph + * the string containing the paragraph text to perform the + * algorithm on. + * @param flags + * indicates the base direction of the bidirectional text. It is + * expected that this will be one of the direction constant + * values defined in this class. An unknown value is treated as + * DIRECTION_DEFAULT_LEFT_TO_RIGHT. + * @see #DIRECTION_LEFT_TO_RIGHT + * @see #DIRECTION_RIGHT_TO_LEFT + * @see #DIRECTION_DEFAULT_RIGHT_TO_LEFT + * @see #DIRECTION_DEFAULT_LEFT_TO_RIGHT + * @since Android 1.0 + */ + public Bidi(String paragraph, int flags) { + this((paragraph == null ? null : paragraph.toCharArray()), 0, null, 0, + (paragraph == null ? 0 : paragraph.length()), flags); + } + + // create the native UBiDi struct, need to be closed with ubidi_close(). + private static long createUBiDi(char[] text, int textStart, + byte[] embeddings, int embStart, int paragraphLength, int flags) { + char[] realText = null; + + byte[] realEmbeddings = null; + + if (text == null || text.length - textStart < paragraphLength) { + throw new IllegalArgumentException(); + } + realText = new char[paragraphLength]; + System.arraycopy(text, textStart, realText, 0, paragraphLength); + + if (embeddings != null) { + if (embeddings.length - embStart < paragraphLength) { + throw new IllegalArgumentException(); + } + if (paragraphLength > 0) { + Bidi temp = new Bidi(text, textStart, null, 0, paragraphLength, + flags); + realEmbeddings = new byte[paragraphLength]; + System.arraycopy(temp.offsetLevel, 0, realEmbeddings, 0, + paragraphLength); + for (int i = 0; i < paragraphLength; i++) { + byte e = embeddings[i]; + if (e < 0) { + realEmbeddings[i] = (byte) (BidiWrapper.UBIDI_LEVEL_OVERRIDE - e); + } else if (e > 0) { + realEmbeddings[i] = e; + } else { + realEmbeddings[i] |= (byte) BidiWrapper.UBIDI_LEVEL_OVERRIDE; + } + } + } + } + + if (flags > 1 || flags < -2) { + flags = 0; + } + + long bidi = BidiWrapper.ubidi_open(); + BidiWrapper.ubidi_setPara(bidi, realText, paragraphLength, + (byte) flags, realEmbeddings); + return bidi; + } + + // private constructor, used by createLineBidi() + private Bidi(long pBidi) { + readBidiInfo(pBidi); + } + + // read info from the native UBiDi struct + private void readBidiInfo(long pBidi) { + + length = BidiWrapper.ubidi_getLength(pBidi); + + offsetLevel = (length == 0) ? null : BidiWrapper.ubidi_getLevels(pBidi); + + baseLevel = BidiWrapper.ubidi_getParaLevel(pBidi); + + int runCount = BidiWrapper.ubidi_countRuns(pBidi); + if (runCount == 0) { + unidirectional = true; + runs = null; + } else if (runCount < 0) { + runs = null; + } else { + runs = BidiWrapper.ubidi_getRuns(pBidi); + + // Simplified case for one run which has the base level + if (runCount == 1 && runs[0].getLevel() == baseLevel) { + unidirectional = true; + runs = null; + } + } + + direction = BidiWrapper.ubidi_getDirection(pBidi); + } + + private int baseLevel; + + private int length; + + private byte[] offsetLevel; + + private BidiRun[] runs; + + private int direction; + + private boolean unidirectional; + + /** + * Returns whether the base level is from left to right. + * + * @return true if the base level is from left to right. + * @since Android 1.0 + */ + public boolean baseIsLeftToRight() { + return baseLevel % 2 == 0 ? true : false; + } + + /** + * Creates a new {@code Bidi} object containing the information of one line + * from this object. + * + * @param lineStart + * the start offset of the line. + * @param lineLimit + * the limit of the line. + * @return the new line Bidi object. In this new object, the indices will + * range from 0 to (limit - start - 1). + * @throws IllegalArgumentException + * if {@code lineStart < 0}, {@code lineLimit < 0}, {@code + * lineStart > lineLimit} or if {@code lineStart} is greater + * than the length of this object's paragraph text. + * @since Android 1.0 + */ + public Bidi createLineBidi(int lineStart, int lineLimit) { + if (lineStart < 0 || lineLimit < 0 || lineLimit > length + || lineStart > lineLimit) { + // text.12=Invalid ranges (start={0}, limit={1}, length={2}) + throw new IllegalArgumentException(Messages.getString( + "text.12", new Object[] { lineStart, lineLimit, length })); //$NON-NLS-1$ + } + char[] text = new char[this.length]; + Arrays.fill(text, 'a'); + byte[] embeddings = new byte[this.length]; + for (int i = 0; i < embeddings.length; i++) { + embeddings[i] = (byte) -this.offsetLevel[i]; + } + + int dir = this.baseIsLeftToRight() ? Bidi.DIRECTION_LEFT_TO_RIGHT + : Bidi.DIRECTION_RIGHT_TO_LEFT; + + long parent = createUBiDi(text, 0, embeddings, 0, this.length, dir); + + long line = BidiWrapper.ubidi_setLine(parent, lineStart, lineLimit); + Bidi result = new Bidi(line); + BidiWrapper.ubidi_close(line); + BidiWrapper.ubidi_close(parent); + return result; + } + + /** + * Returns the base level. + * + * @return the base level. + * @since Android 1.0 + */ + public int getBaseLevel() { + return baseLevel; + } + + /** + * Returns the length of the text in the {@code Bidi} object. + * + * @return the length. + * @since Android 1.0 + */ + public int getLength() { + return length; + } + + /** + * Returns the level of a specified character. + * + * @param offset + * the offset of the character. + * @return the level. + * @since Android 1.0 + */ + public int getLevelAt(int offset) { + try { + return offsetLevel[offset] & ~BidiWrapper.UBIDI_LEVEL_OVERRIDE; + } catch (RuntimeException e) { + return baseLevel; + } + } + + /** + * Returns the number of runs in the bidirectional text. + * + * @return the number of runs, at least 1. + * @since Android 1.0 + */ + public int getRunCount() { + return unidirectional ? 1 : runs.length; + } + + /** + * Returns the level of the specified run. + * + * @param run + * the index of the run. + * @return the level of the run. + * @since Android 1.0 + */ + public int getRunLevel(int run) { + return unidirectional ? baseLevel : runs[run].getLevel(); + } + + /** + * Returns the limit offset of the specified run. + * + * @param run + * the index of the run. + * @return the limit offset of the run. + * @since Android 1.0 + */ + public int getRunLimit(int run) { + return unidirectional ? length : runs[run].getLimit(); + } + + /** + * Returns the start offset of the specified run. + * + * @param run + * the index of the run. + * @return the start offset of the run. + * @since Android 1.0 + */ + public int getRunStart(int run) { + return unidirectional ? 0 : runs[run].getStart(); + } + + /** + * Indicates whether the text is from left to right, that is, both the base + * direction and the text direction is from left to right. + * + * @return {@code true} if the text is from left to right; {@code false} + * otherwise. + * @since Android 1.0 + */ + public boolean isLeftToRight() { + return direction == BidiWrapper.UBiDiDirection_UBIDI_LTR; + } + + /** + * Indicates whether the text direction is mixed. + * + * @return {@code true} if the text direction is mixed; {@code false} + * otherwise. + * @since Android 1.0 + */ + public boolean isMixed() { + return direction == BidiWrapper.UBiDiDirection_UBIDI_MIXED; + } + + /** + * Indicates whether the text is from right to left, that is, both the base + * direction and the text direction is from right to left. + * + * @return {@code true} if the text is from right to left; {@code false} + * otherwise. + * @since Android 1.0 + */ + public boolean isRightToLeft() { + return direction == BidiWrapper.UBiDiDirection_UBIDI_RTL; + } + + /** + * Reorders a range of objects according to their specified levels. This is + * a convenience function that does not use a {@code Bidi} object. The range + * of objects at {@code index} from {@code objectStart} to {@code + * objectStart + count} will be reordered according to the range of levels + * at {@code index} from {@code levelStart} to {@code levelStart + count}. + * + * @param levels + * the level array, which is already determined. + * @param levelStart + * the start offset of the range of the levels. + * @param objects + * the object array to reorder. + * @param objectStart + * the start offset of the range of objects. + * @param count + * the count of the range of objects to reorder. + * @throws IllegalArgumentException + * if {@code count}, {@code levelStart} or {@code objectStart} + * is negative; if {@code count > levels.length - levelStart} or + * if {@code count > objects.length - objectStart}. + * @since Android 1.0 + */ + public static void reorderVisually(byte[] levels, int levelStart, + Object[] objects, int objectStart, int count) { + if (count < 0 || levelStart < 0 || objectStart < 0 + || count > levels.length - levelStart + || count > objects.length - objectStart) { + // text.13=Invalid ranges (levels={0}, levelStart={1}, objects={2}, + // objectStart={3}, count={4}) + throw new IllegalArgumentException(Messages.getString("text.13", //$NON-NLS-1$ + new Object[] { levels.length, levelStart, objects.length, + objectStart, count })); + } + byte[] realLevels = new byte[count]; + System.arraycopy(levels, levelStart, realLevels, 0, count); + + int[] indices = BidiWrapper.ubidi_reorderVisual(realLevels, count); + + LinkedList<Object> result = new LinkedList<Object>(); + for (int i = 0; i < count; i++) { + result.addLast(objects[objectStart + indices[i]]); + } + + System.arraycopy(result.toArray(), 0, objects, objectStart, count); + } + + /** + * Indicates whether a range of characters of a text requires a {@code Bidi} + * object to display properly. + * + * @param text + * the char array of the text. + * @param start + * the start offset of the range of characters. + * @param limit + * the limit offset of the range of characters. + * @return {@code true} if the range of characters requires a {@code Bidi} + * object; {@code false} otherwise. + * @throws IllegalArgumentException + * if {@code start} or {@code limit} is negative; {@code start > + * limit} or {@code limit} is greater than the length of this + * object's paragraph text. + * @since Android 1.0 + */ + public static boolean requiresBidi(char[] text, int start, int limit) { + int length = text.length; + if (limit < 0 || start < 0 || start > limit || limit > length) { + throw new IllegalArgumentException(); + } + Bidi bidi = new Bidi(text, start, null, 0, limit - start, 0); + return !bidi.isLeftToRight(); + } + + /** + * Returns the internal message of the {@code Bidi} object, used in + * debugging. + * + * @return a string containing the internal message. + * @since Android 1.0 + */ + @Override + public String toString() { + return super.toString() + + "[direction: " + direction + " baselevel: " + baseLevel //$NON-NLS-1$ //$NON-NLS-2$ + + " length: " + length + " runs: " + (unidirectional ? "null" : runs.toString()) + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } +} diff --git a/text/src/main/java/java/text/BreakIterator.java b/text/src/main/java/java/text/BreakIterator.java new file mode 100644 index 0000000..76b848e --- /dev/null +++ b/text/src/main/java/java/text/BreakIterator.java @@ -0,0 +1,690 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** +******************************************************************************* +* Copyright (C) 1996-2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc and some of the method descriptions are copied from ICU4J +// source files. Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// The icu implementation used was changed from icu4j to icu4jni. +// END android-note + +package java.text; + +import java.util.Locale; + +/** + * Locates boundaries in text. This class defines a protocol for objects that + * break up a piece of natural-language text according to a set of criteria. + * Instances or subclasses of {@code BreakIterator} can be provided, for + * example, to break a piece of text into words, sentences, or logical + * characters according to the conventions of some language or group of + * languages. We provide four built-in types of {@code BreakIterator}: + * <ul> + * <li>{@link #getSentenceInstance()} returns a {@code BreakIterator} that + * locates boundaries between sentences. This is useful for triple-click + * selection, for example.</li> + * <li>{@link #getWordInstance()} returns a {@code BreakIterator} that locates + * boundaries between words. This is useful for double-click selection or "find + * whole words" searches. This type of {@code BreakIterator} makes sure there is + * a boundary position at the beginning and end of each legal word (numbers + * count as words, too). Whitespace and punctuation are kept separate from real + * words.</li> + * <li>{@code getLineInstance()} returns a {@code BreakIterator} that locates + * positions where it is legal for a text editor to wrap lines. This is similar + * to word breaking, but not the same: punctuation and whitespace are generally + * kept with words (you don't want a line to start with whitespace, for + * example), and some special characters can force a position to be considered a + * line break position or prevent a position from being a line break position.</li> + * <li>{@code getCharacterInstance()} returns a {@code BreakIterator} that + * locates boundaries between logical characters. Because of the structure of + * the Unicode encoding, a logical character may be stored internally as more + * than one Unicode code point. (A with an umlaut may be stored as an a followed + * by a separate combining umlaut character, for example, but the user still + * thinks of it as one character.) This iterator allows various processes + * (especially text editors) to treat as characters the units of text that a + * user would think of as characters, rather than the units of text that the + * computer sees as "characters".</li> + * </ul> {@code BreakIterator}'s interface follows an "iterator" model (hence + * the name), meaning it has a concept of a "current position" and methods like + * {@code first()}, {@code last()}, {@code next()}, and {@code previous()} that + * update the current position. All {@code BreakIterator}s uphold the following + * invariants: + * <ul> + * <li>The beginning and end of the text are always treated as boundary + * positions.</li> + * <li>The current position of the iterator is always a boundary position + * (random- access methods move the iterator to the nearest boundary position + * before or after the specified position, not <i>to</i> the specified + * position).</li> + * <li>{@code DONE} is used as a flag to indicate when iteration has stopped. + * {@code DONE} is only returned when the current position is the end of the + * text and the user calls {@code next()}, or when the current position is the + * beginning of the text and the user calls {@code previous()}.</li> + * <li>Break positions are numbered by the positions of the characters that + * follow them. Thus, under normal circumstances, the position before the first + * character is 0, the position after the first character is 1, and the position + * after the last character is 1 plus the length of the string.</li> + * <li>The client can change the position of an iterator, or the text it + * analyzes, at will, but cannot change the behavior. If the user wants + * different behavior, he must instantiate a new iterator.</li> + * </ul> + * <p> + * {@code BreakIterator} accesses the text it analyzes through a + * {@link CharacterIterator}, which makes it possible to use {@code + * BreakIterator} to analyze text in any text-storage vehicle that provides a + * {@code CharacterIterator} interface. + * </p> + * <p> + * <em>Note:</em> Some types of {@code BreakIterator} can take a long time to + * create, and instances of {@code BreakIterator} are not currently cached by + * the system. For optimal performance, keep instances of {@code BreakIterator} + * around as long as it makes sense. For example, when word-wrapping a document, + * don't create and destroy a new {@code BreakIterator} for each line. Create + * one break iterator for the whole document (or whatever stretch of text you're + * wrapping) and use it to do the whole job of wrapping the text. + * <p> + * <em>Examples</em>: + * </p> + * <p> + * Creating and using text boundaries: + * </p> + * <blockquote> + * + * <pre> + * public static void main(String args[]) { + * if (args.length == 1) { + * String stringToExamine = args[0]; + * //print each word in order + * BreakIterator boundary = BreakIterator.getWordInstance(); + * boundary.setText(stringToExamine); + * printEachForward(boundary, stringToExamine); + * //print each sentence in reverse order + * boundary = BreakIterator.getSentenceInstance(Locale.US); + * boundary.setText(stringToExamine); + * printEachBackward(boundary, stringToExamine); + * printFirst(boundary, stringToExamine); + * printLast(boundary, stringToExamine); + * } + * } + * </pre> + * + * </blockquote> + * <p> + * Print each element in order: + * </p> + * <blockquote> + * + * <pre> + * public static void printEachForward(BreakIterator boundary, String source) { + * int start = boundary.first(); + * for (int end = boundary.next(); end != BreakIterator.DONE; start = end, end = boundary.next()) { + * System.out.println(source.substring(start, end)); + * } + * } + * </pre> + * + * </blockquote> + * <p> + * Print each element in reverse order: + * </p> + * <blockquote> + * + * <pre> + * public static void printEachBackward(BreakIterator boundary, String source) { + * int end = boundary.last(); + * for (int start = boundary.previous(); start != BreakIterator.DONE; end = start, start = boundary + * .previous()) { + * System.out.println(source.substring(start, end)); + * } + * } + * </pre> + * + * </blockquote> + * <p> + * Print the first element: + * </p> + * <blockquote> + * + * <pre> + * public static void printFirst(BreakIterator boundary, String source) { + * int start = boundary.first(); + * int end = boundary.next(); + * System.out.println(source.substring(start, end)); + * } + * </pre> + * + * </blockquote> + * <p> + * Print the last element: + * </p> + * <blockquote> + * + * <pre> + * public static void printLast(BreakIterator boundary, String source) { + * int end = boundary.last(); + * int start = boundary.previous(); + * System.out.println(source.substring(start, end)); + * } + * </pre> + * + * </blockquote> + * <p> + * Print the element at a specified position: + * </p> + * <blockquote> + * + * <pre> + * public static void printAt(BreakIterator boundary, int pos, String source) { + * int end = boundary.following(pos); + * int start = boundary.previous(); + * System.out.println(source.substring(start, end)); + * } + * </pre> + * + * </blockquote> + * <p> + * Find the next word: + * </p> + * <blockquote> + * + * <pre> + * public static int nextWordStartAfter(int pos, String text) { + * BreakIterator wb = BreakIterator.getWordInstance(); + * wb.setText(text); + * int last = wb.following(pos); + * int current = wb.next(); + * while (current != BreakIterator.DONE) { + * for (int p = last; p < current; p++) { + * if (Character.isLetter(text.charAt(p))) + * return last; + * } + * last = current; + * current = wb.next(); + * } + * return BreakIterator.DONE; + * } + * </pre> + * + * </blockquote> + * <p> + * The iterator returned by {@code BreakIterator.getWordInstance()} is unique in + * that the break positions it returns don't represent both the start and end of + * the thing being iterated over. That is, a sentence-break iterator returns + * breaks that each represent the end of one sentence and the beginning of the + * next. With the word-break iterator, the characters between two boundaries + * might be a word, or they might be the punctuation or whitespace between two + * words. The above code uses a simple heuristic to determine which boundary is + * the beginning of a word: If the characters between this boundary and the next + * boundary include at least one letter (this can be an alphabetical letter, a + * CJK ideograph, a Hangul syllable, a Kana character, etc.), then the text + * between this boundary and the next is a word; otherwise, it's the material + * between words.) + * </p> + * + * @see CharacterIterator + * @since Android 1.0 + */ +public abstract class BreakIterator implements Cloneable { + + /* + * ----------------------------------------------------------------------- + * constants + * ----------------------------------------------------------------------- + */ + /** + * This constant is returned by iterate methods like {@code previous()} or + * {@code next()} if they have returned all valid boundaries. + * + * @since Android 1.0 + */ + public static final int DONE = -1; + + private static final int LONG_LENGTH = 8; + + private static final int INT_LENGTH = 4; + + private static final int SHORT_LENGTH = 2; + + /* + * ----------------------------------------------------------------------- + * variables + * ----------------------------------------------------------------------- + */ + // the wrapped ICU implementation + com.ibm.icu4jni.text.BreakIterator wrapped; + + /* + * ----------------------------------------------------------------------- + * constructors + * ----------------------------------------------------------------------- + */ + /** + * Default constructor, just for invocation by a subclass. + * + * @since Android 1.0 + */ + protected BreakIterator() { + super(); + } + + /* + * wrapping constructor + */ + BreakIterator(com.ibm.icu4jni.text.BreakIterator iterator) { + wrapped = iterator; + } + + /* + * ----------------------------------------------------------------------- + * methods + * ----------------------------------------------------------------------- + */ + /** + * Returns all supported locales in an array. + * + * @return all supported locales. + * @since Android 1.0 + */ + public static Locale[] getAvailableLocales() { + return com.ibm.icu4jni.text.BreakIterator.getAvailableLocales(); + } + + /** + * Returns a new instance of {@code BreakIterator} to iterate over + * characters using the default locale. + * + * @return a new instance of {@code BreakIterator} using the default locale. + * @since Android 1.0 + */ + public static BreakIterator getCharacterInstance() { + return new RuleBasedBreakIterator(com.ibm.icu4jni.text.BreakIterator + .getCharacterInstance()); + } + + /** + * Returns a new instance of {@code BreakIterator} to iterate over + * characters using the given locale. + * + * @param where + * the given locale. + * @return a new instance of {@code BreakIterator} using the given locale. + * @since Android 1.0 + */ + public static BreakIterator getCharacterInstance(Locale where) { + if (where == null) { + throw new NullPointerException(); + } + + return new RuleBasedBreakIterator(com.ibm.icu4jni.text.BreakIterator + .getCharacterInstance(where)); + } + + /** + * Returns a new instance of {{@code BreakIterator} to iterate over + * line breaks using the default locale. + * + * @return a new instance of {@code BreakIterator} using the default locale. + * @since Android 1.0 + */ + public static BreakIterator getLineInstance() { + return new RuleBasedBreakIterator(com.ibm.icu4jni.text.BreakIterator + .getLineInstance()); + } + + /** + * Returns a new instance of {@code BreakIterator} to iterate over + * line breaks using the given locale. + * + * @param where + * the given locale. + * @return a new instance of {@code BreakIterator} using the given locale. + * @throws NullPointerException if {@code where} is {@code null}. + * @since Android 1.0 + */ + public static BreakIterator getLineInstance(Locale where) { + if (where == null) { + throw new NullPointerException(); + } + + return new RuleBasedBreakIterator(com.ibm.icu4jni.text.BreakIterator + .getLineInstance(where)); + } + + /** + * Returns a new instance of {@code BreakIterator} to iterate over + * sentence-breaks using the default locale. + * + * @return a new instance of {@code BreakIterator} using the default locale. + * @since Android 1.0 + */ + public static BreakIterator getSentenceInstance() { + return new RuleBasedBreakIterator(com.ibm.icu4jni.text.BreakIterator + .getSentenceInstance()); + } + + /** + * Returns a new instance of {@code BreakIterator} to iterate over + * sentence-breaks using the given locale. + * + * @param where + * the given locale. + * @return a new instance of {@code BreakIterator} using the given locale. + * @throws NullPointerException if {@code where} is {@code null}. + * @since Android 1.0 + */ + public static BreakIterator getSentenceInstance(Locale where) { + if (where == null) { + throw new NullPointerException(); + } + + return new RuleBasedBreakIterator(com.ibm.icu4jni.text.BreakIterator + .getSentenceInstance(where)); + } + + /** + * Returns a new instance of {@code BreakIterator} to iterate over + * word-breaks using the default locale. + * + * @return a new instance of {@code BreakIterator} using the default locale. + * @since Android 1.0 + */ + public static BreakIterator getWordInstance() { + return new RuleBasedBreakIterator(com.ibm.icu4jni.text.BreakIterator + .getWordInstance()); + } + + /** + * Returns a new instance of {@code BreakIterator} to iterate over + * word-breaks using the given locale. + * + * @param where + * the given locale. + * @return a new instance of {@code BreakIterator} using the given locale. + * @throws NullPointerException if {@code where} is {@code null}. + * @since Android 1.0 + */ + public static BreakIterator getWordInstance(Locale where) { + if (where == null) { + throw new NullPointerException(); + } + + return new RuleBasedBreakIterator(com.ibm.icu4jni.text.BreakIterator + .getWordInstance(where)); + } + + /** + * Indicates whether the given offset is a boundary position. If this method + * returns true, the current iteration position is set to the given + * position; if the function returns false, the current iteration position + * is set as though {@link #following(int)} had been called. + * + * @param offset + * the given offset to check. + * @return {@code true} if the given offset is a boundary position; {@code + * false} otherwise. + * @since Android 1.0 + */ + public boolean isBoundary(int offset) { + return wrapped.isBoundary(offset); + } + + /** + * Returns the position of last boundary preceding the given offset, and + * sets the current position to the returned value, or {@code DONE} if the + * given offset specifies the starting position. + * + * @param offset + * the given start position to be searched for. + * @return the position of the last boundary preceding the given offset. + * @since Android 1.0 + */ + public int preceding(int offset) { + return wrapped.preceding(offset); + } + + /** + * Sets the new text string to be analyzed, the current position will be + * reset to the beginning of this new string, and the old string will be + * lost. + * + * @param newText + * the new text string to be analyzed. + * @since Android 1.0 + */ + public void setText(String newText) { + wrapped.setText(newText); + } + + /* + * ----------------------------------------------------------------------- + * abstract methods + * ----------------------------------------------------------------------- + */ + /** + * Returns this iterator's current position. + * + * @return this iterator's current position. + * @since Android 1.0 + */ + public abstract int current(); + + /** + * Sets this iterator's current position to the first boundary and returns + * that position. + * + * @return the position of the first boundary. + * @since Android 1.0 + */ + public abstract int first(); + + /** + * Sets the position of the first boundary to the one following the given + * offset and returns this position. Returns {@code DONE} if there is no + * boundary after the given offset. + * + * @param offset + * the given position to be searched for. + * @return the position of the first boundary following the given offset. + * @since Android 1.0 + */ + public abstract int following(int offset); + + /** + * Returns a {@code CharacterIterator} which represents the text being + * analyzed. Please note that the returned value is probably the internal + * iterator used by this object. If the invoker wants to modify the status + * of the returned iterator, it is recommended to first create a clone of + * the iterator returned. + * + * @return a {@code CharacterIterator} which represents the text being + * analyzed. + * @since Android 1.0 + */ + public abstract CharacterIterator getText(); + + /** + * Sets this iterator's current position to the last boundary and returns + * that position. + * + * @return the position of last boundary. + * @since Android 1.0 + */ + public abstract int last(); + + /** + * Sets this iterator's current position to the next boundary after the + * current position, and returns this position. Returns {@code DONE} if no + * boundary was found after the current position. + * + * @return the position of last boundary. + * @since Android 1.0 + */ + public abstract int next(); + + /** + * Sets this iterator's current position to the next boundary after the + * given position, and returns that position. Returns {@code DONE} if no + * boundary was found after the given position. + * + * @param n + * the given position. + * @return the position of last boundary. + * @since Android 1.0 + */ + public abstract int next(int n); + + /** + * Sets this iterator's current position to the previous boundary before the + * current position and returns that position. Returns {@code DONE} if + * no boundary was found before the current position. + * + * @return the position of last boundary. + * @since Android 1.0 + */ + public abstract int previous(); + + /** + * Sets the new text to be analyzed by the given {@code CharacterIterator}. + * The position will be reset to the beginning of the new text, and other + * status information of this iterator will be kept. + * + * @param newText + * the {@code CharacterIterator} referring to the text to be + * analyzed. + * @since Android 1.0 + */ + public abstract void setText(CharacterIterator newText); + + /* + * ----------------------------------------------------------------------- + * methods override Object + * ----------------------------------------------------------------------- + */ + /** + * Creates a copy of this iterator, all status information including the + * current position are kept the same. + * + * @return a copy of this iterator. + * @since Android 1.0 + */ + @Override + public Object clone() { + try { + BreakIterator cloned = (BreakIterator) super.clone(); + cloned.wrapped = (com.ibm.icu4jni.text.BreakIterator) wrapped.clone(); + return cloned; + } catch (CloneNotSupportedException e) { + throw new InternalError(e.getMessage()); + } + } + + /** + * Gets a long value from the given byte array, starting from the given + * offset. + * + * @param buf + * the bytes to be converted. + * @param offset + * the start position of the conversion. + * @return the converted long value. + * @throws NullPointerException + * if {@code buf} is {@code null}. + * @throws ArrayIndexOutOfBoundsException + * if {@code offset < 0} or {@code offset + LONG_LENGTH} is + * greater than the length of {@code buf}. + * @since Android 1.0 + */ + protected static long getLong(byte[] buf, int offset) { + if (null == buf) { + throw new NullPointerException(); + } + if (offset < 0 || buf.length - offset < LONG_LENGTH) { + throw new ArrayIndexOutOfBoundsException(); + } + long result = 0; + for (int i = offset; i < offset + LONG_LENGTH; i++) { + result = (result << 8) | (buf[i] & 0xff); + } + return result; + } + + /** + * Gets an int value from the given byte array, starting from the given + * offset. + * + * @param buf + * the bytes to be converted. + * @param offset + * the start position of the conversion. + * @return the converted int value. + * @throws NullPointerException + * if {@code buf} is {@code null}. + * @throws ArrayIndexOutOfBoundsException + * if {@code offset < 0} or {@code offset + INT_LENGTH} is + * greater than the length of {@code buf}. + * @since Android 1.0 + */ + protected static int getInt(byte[] buf, int offset) { + if (null == buf) { + throw new NullPointerException(); + } + if (offset < 0 || buf.length - INT_LENGTH < offset) { + throw new ArrayIndexOutOfBoundsException(); + } + int result = 0; + for (int i = offset; i < offset + INT_LENGTH; i++) { + result = (result << 8) | (buf[i] & 0xff); + } + return result; + } + + /** + * Gets a short value from the given byte array, starting from the given + * offset. + * + * @param buf + * the bytes to be converted. + * @param offset + * the start position of the conversion. + * @return the converted short value. + * @throws NullPointerException + * if {@code buf} is {@code null}. + * @throws ArrayIndexOutOfBoundsException + * if {@code offset < 0} or {@code offset + SHORT_LENGTH} is + * greater than the length of {@code buf}. + * @since Android 1.0 + */ + protected static short getShort(byte[] buf, int offset) { + if (null == buf) { + throw new NullPointerException(); + } + if (offset < 0 || buf.length - SHORT_LENGTH < offset) { + throw new ArrayIndexOutOfBoundsException(); + } + short result = 0; + for (int i = offset; i < offset + SHORT_LENGTH; i++) { + result = (short) ((result << 8) | (buf[i] & 0xff)); + } + return result; + } +} diff --git a/text/src/main/java/java/text/CharacterIterator.java b/text/src/main/java/java/text/CharacterIterator.java new file mode 100644 index 0000000..dfcd21d --- /dev/null +++ b/text/src/main/java/java/text/CharacterIterator.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.text; + +/** + * An interface for the bidirectional iteration over a group of characters. The + * iteration starts at the begin index in the group of characters and continues + * to one index before the end index. + * + * @since Android 1.0 + */ +public interface CharacterIterator extends Cloneable { + + /** + * A constant which indicates that there is no character at the current + * index. + * + * @since Android 1.0 + */ + public static final char DONE = '\uffff'; + + /** + * Returns a new {@code CharacterIterator} with the same properties. + * + * @return a shallow copy of this character iterator. + * + * @see java.lang.Cloneable + * @since Android 1.0 + */ + public Object clone(); + + /** + * Returns the character at the current index. + * + * @return the current character, or {@code DONE} if the current index is + * past the beginning or end of the sequence. + * @since Android 1.0 + */ + public char current(); + + /** + * Sets the current position to the begin index and returns the character at + * the new position. + * + * @return the character at the begin index. + * @since Android 1.0 + */ + public char first(); + + /** + * Returns the begin index. + * + * @return the index of the first character of the iteration. + * @since Android 1.0 + */ + public int getBeginIndex(); + + /** + * Returns the end index. + * + * @return the index one past the last character of the iteration. + * @since Android 1.0 + */ + public int getEndIndex(); + + /** + * Returns the current index. + * + * @return the current index. + * @since Android 1.0 + */ + public int getIndex(); + + /** + * Sets the current position to the end index - 1 and returns the character + * at the new position. + * + * @return the character before the end index. + * @since Android 1.0 + */ + public char last(); + + /** + * Increments the current index and returns the character at the new index. + * + * @return the character at the next index, or {@code DONE} if the next + * index would be past the end. + * @since Android 1.0 + */ + public char next(); + + /** + * Decrements the current index and returns the character at the new index. + * + * @return the character at the previous index, or {@code DONE} if the + * previous index would be past the beginning. + * @since Android 1.0 + */ + public char previous(); + + /** + * Sets the current index to a new position and returns the character at the + * new index. + * + * @param location + * the new index that this character iterator is set to. + * @return the character at the new index, or {@code DONE} if the index is + * past the end. + * @exception IllegalArgumentException + * if {@code location} is less than the begin index or + * greater than the end index. + * @since Android 1.0 + */ + public char setIndex(int location); +} diff --git a/text/src/main/java/java/text/ChoiceFormat.java b/text/src/main/java/java/text/ChoiceFormat.java new file mode 100644 index 0000000..41daced --- /dev/null +++ b/text/src/main/java/java/text/ChoiceFormat.java @@ -0,0 +1,507 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** +******************************************************************************* +* Copyright (C) 1996-2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc description is copied from ICU UserGuide. +// Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// END android-note + +package java.text; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * Returns a fixed string based on a numeric value. The class can be used in + * conjunction with the {@link MessageFormat} class to handle plurals in + * messages. {@code ChoiceFormat} enables users to attach a format to a range of + * numbers. The choice is specified with an ascending list of doubles, where + * each item specifies a half-open interval up to the next item as in the + * following: X matches j if and only if {@code limit[j] <= X < limit[j+1]}. + * <p> + * If there is no match, then either the first or last index is used. The first + * or last index is used depending on whether the number is too low or too high. + * The length of the format array must be the same as the length of the limits + * array. + * </p> + * <h5>Examples:</h5> + * <blockquote> + * + * <pre> + * double[] limits = {1, 2, 3, 4, 5, 6, 7}; + * String[] fmts = {"Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat"}; + * + * double[] limits2 = {0, 1, ChoiceFormat.nextDouble(1)}; + * String[] fmts2 = {"no files", "one file", "many files"}; + * </pre> + * </blockquote> + * <p> + * ChoiceFormat.nextDouble(double) allows to get the double following the one + * passed to the method. This is used to create half open intervals. + * </p> + * <p> + * {@code ChoiceFormat} objects also may be converted to and from patterns. + * The conversion can be done programmatically, as in the example above, or + * by using a pattern like the following: + * </p> + * <blockquote> + * + * <pre> + * "1#Sun|2#Mon|3#Tue|4#Wed|5#Thur|6#Fri|7#Sat" + * "0#are no files|1#is one file|1<are many files" + * </pre> + * + * </blockquote> + * <p> + * where: + * </p> + * <ul> + * <li><number>"#"</number> specifies an inclusive limit value;</li> + * <li><number>"<"</number> specifies an exclusive limit value.</li> + * </ul> + * + * @since Android 1.0 + */ +public class ChoiceFormat extends NumberFormat { + + private static final long serialVersionUID = 1795184449645032964L; + + private double[] choiceLimits; + + private String[] choiceFormats; + + /** + * Constructs a new {@code ChoiceFormat} with the specified double values + * and associated strings. When calling + * {@link #format(double, StringBuffer, FieldPosition) format} with a double + * value {@code d}, then the element {@code i} in {@code formats} is + * selected where {@code i} fulfills {@code limits[i] <= d < limits[i+1]}. + * <p> + * The length of the {@code limits} and {@code formats} arrays must be the + * same. + * </p> + * + * @param limits + * an array of doubles in ascending order. The lowest and highest + * possible values are negative and positive infinity. + * @param formats + * the strings associated with the ranges defined through {@code + * limits}. The lower bound of the associated range is at the + * same index as the string. + * @since Android 1.0 + */ + public ChoiceFormat(double[] limits, String[] formats) { + setChoices(limits, formats); + } + + /** + * Constructs a new {@code ChoiceFormat} with the strings and limits parsed + * from the specified pattern. + * + * @param template + * the pattern of strings and ranges. + * + * @exception IllegalArgumentException + * if an error occurs while parsing the pattern. + * @since Android 1.0 + */ + public ChoiceFormat(String template) { + applyPattern(template); + } + + /** + * Parses the pattern to determine new strings and ranges for this + * {@code ChoiceFormat}. + * + * @param template + * the pattern of strings and ranges. + * + * @exception IllegalArgumentException + * if an error occurs while parsing the pattern. + * @since Android 1.0 + */ + public void applyPattern(String template) { + double[] limits = new double[5]; + List<String> formats = new ArrayList<String>(); + int length = template.length(), limitCount = 0, index = 0; + StringBuffer buffer = new StringBuffer(); + NumberFormat format = NumberFormat.getInstance(Locale.US); + ParsePosition position = new ParsePosition(0); + while (true) { + index = skipWhitespace(template, index); + if (index >= length) { + if (limitCount == limits.length) { + choiceLimits = limits; + } else { + choiceLimits = new double[limitCount]; + System.arraycopy(limits, 0, choiceLimits, 0, limitCount); + } + choiceFormats = new String[formats.size()]; + for (int i = 0; i < formats.size(); i++) { + choiceFormats[i] = formats.get(i); + } + return; + } + + position.setIndex(index); + Number value = format.parse(template, position); + index = skipWhitespace(template, position.getIndex()); + if (position.getErrorIndex() != -1 || index >= length) { + // Fix Harmony 540 + choiceLimits = new double[0]; + choiceFormats = new String[0]; + return; + } + char ch = template.charAt(index++); + if (limitCount == limits.length) { + double[] newLimits = new double[limitCount * 2]; + System.arraycopy(limits, 0, newLimits, 0, limitCount); + limits = newLimits; + } + double next; + switch (ch) { + case '#': + case '\u2264': + next = value.doubleValue(); + break; + case '<': + next = nextDouble(value.doubleValue()); + break; + default: + throw new IllegalArgumentException(); + } + if (limitCount > 0 && next <= limits[limitCount - 1]) { + throw new IllegalArgumentException(); + } + buffer.setLength(0); + position.setIndex(index); + upTo(template, position, buffer, '|'); + index = position.getIndex(); + limits[limitCount++] = next; + formats.add(buffer.toString()); + } + } + + /** + * Returns a new instance of {@code ChoiceFormat} with the same ranges and + * strings as this {@code ChoiceFormat}. + * + * @return a shallow copy of this {@code ChoiceFormat}. + * + * @see java.lang.Cloneable + * @since Android 1.0 + */ + @Override + public Object clone() { + ChoiceFormat clone = (ChoiceFormat) super.clone(); + clone.choiceLimits = choiceLimits.clone(); + clone.choiceFormats = choiceFormats.clone(); + return clone; + } + + /** + * Compares the specified object with this {@code ChoiceFormat}. The object + * must be an instance of {@code ChoiceFormat} and have the same limits and + * formats to be equal to this instance. + * + * @param object + * the object to compare with this instance. + * @return {@code true} if the specified object is equal to this instance; + * {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof ChoiceFormat)) { + return false; + } + ChoiceFormat choice = (ChoiceFormat) object; + return Arrays.equals(choiceLimits, choice.choiceLimits) + && Arrays.equals(choiceFormats, choice.choiceFormats); + } + + /** + * Appends the string associated with the range in which the specified + * double value fits to the specified string buffer. + * + * @param value + * the double to format. + * @param buffer + * the target string buffer to append the formatted value to. + * @param field + * a {@code FieldPosition} which is ignored. + * @return the string buffer. + * @since Android 1.0 + */ + @Override + public StringBuffer format(double value, StringBuffer buffer, + FieldPosition field) { + for (int i = choiceLimits.length - 1; i >= 0; i--) { + if (choiceLimits[i] <= value) { + return buffer.append(choiceFormats[i]); + } + } + return choiceFormats.length == 0 ? buffer : buffer + .append(choiceFormats[0]); + } + + /** + * Appends the string associated with the range in which the specified long + * value fits to the specified string buffer. + * + * @param value + * the long to format. + * @param buffer + * the target string buffer to append the formatted value to. + * @param field + * a {@code FieldPosition} which is ignored. + * @return the string buffer. + * @since Android 1.0 + */ + @Override + public StringBuffer format(long value, StringBuffer buffer, + FieldPosition field) { + return format((double) value, buffer, field); + } + + /** + * Returns the strings associated with the ranges of this {@code + * ChoiceFormat}. + * + * @return an array of format strings. + * @since Android 1.0 + */ + public Object[] getFormats() { + return choiceFormats; + } + + /** + * Returns the limits of this {@code ChoiceFormat}. + * + * @return the array of doubles which make up the limits of this {@code + * ChoiceFormat}. + * @since Android 1.0 + */ + public double[] getLimits() { + return choiceLimits; + } + + /** + * Returns an integer hash code for the receiver. Objects which are equal + * return the same value for this method. + * + * @return the receiver's hash. + * + * @see #equals + * @since Android 1.0 + */ + @Override + public int hashCode() { + int hashCode = 0; + for (int i = 0; i < choiceLimits.length; i++) { + long v = Double.doubleToLongBits(choiceLimits[i]); + hashCode += (int) (v ^ (v >>> 32)) + choiceFormats[i].hashCode(); + } + return hashCode; + } + + /** + * Returns the double value which is closest to the specified double but + * larger. + * + * @param value + * a double value. + * @return the next larger double value. + * @since Android 1.0 + */ + public static final double nextDouble(double value) { + if (value == Double.POSITIVE_INFINITY) { + return value; + } + long bits; + // Handle -0.0 + if (value == 0) { + bits = 0; + } else { + bits = Double.doubleToLongBits(value); + } + return Double.longBitsToDouble(value < 0 ? bits - 1 : bits + 1); + } + + /** + * Returns the double value which is closest to the specified double but + * either larger or smaller as specified. + * + * @param value + * a double value. + * @param increment + * {@code true} to get the next larger value, {@code false} to + * get the previous smaller value. + * @return the next larger or smaller double value. + * @since Android 1.0 + */ + public static double nextDouble(double value, boolean increment) { + return increment ? nextDouble(value) : previousDouble(value); + } + + /** + * Parses a double from the specified string starting at the index specified + * by {@code position}. The string is compared to the strings of this + * {@code ChoiceFormat} and if a match occurs then the lower bound of the + * corresponding range in the limits array is returned. If the string is + * successfully parsed then the index of the {@code ParsePosition} passed to + * this method is updated to the index following the parsed text. + * + * @param string + * the source string to parse. + * @param position + * input/output parameter, specifies the start index in {@code string} + * from where to start parsing. See the <em>Returns</em> section for + * a description of the output values. + * @return if one of the format strings of this {@code ChoiceFormat} instance + * is found in {@code string} starting at the index specified by {@code position.getIndex()} then + * <ul> + * <li>the index in {@code position} is set to the index following the parsed text; + * <li>the {@link java.lang.Double Double} corresponding to the format string is returned.</li> + * </ul> + * <p> + * If none of the format strings is found in {@code string} then + * <ul> + * <li>the error index in {@code position} is set to the current index in {@code position};</li> + * <li> {@link java.lang.Double#NaN Double.NaN} is returned. + * </ul> + * @since Android 1.0 + */ + @Override + public Number parse(String string, ParsePosition position) { + int offset = position.getIndex(); + for (int i = 0; i < choiceFormats.length; i++) { + if (string.startsWith(choiceFormats[i], offset)) { + position.setIndex(offset + choiceFormats[i].length()); + return new Double(choiceLimits[i]); + } + } + position.setErrorIndex(offset); + return new Double(Double.NaN); + } + + /** + * Returns the double value which is closest to the specified double but + * smaller. + * + * @param value + * a double value. + * @return the next smaller double value. + * @since Android 1.0 + */ + public static final double previousDouble(double value) { + if (value == Double.NEGATIVE_INFINITY) { + return value; + } + long bits; + // Handle 0.0 + if (value == 0) { + bits = 0x8000000000000000L; + } else { + bits = Double.doubleToLongBits(value); + } + return Double.longBitsToDouble(value <= 0 ? bits + 1 : bits - 1); + } + + /** + * Sets the double values and associated strings of this ChoiceFormat. When + * calling {@link #format(double, StringBuffer, FieldPosition) format} with + * a double value {@code d}, then the element {@code i} in {@code formats} + * is selected where {@code i} fulfills + * {@code limits[i] <= d < limits[i+1]}. + * <p> + * The length of the {@code limits} and {@code formats} arrays must be the + * same. + * </p> + * + * @param limits + * an array of doubles in ascending order. The lowest and highest + * possible values are negative and positive infinity. + * @param formats + * the strings associated with the ranges defined through {@code + * limits}. The lower bound of the associated range is at the + * same index as the string. + * @since Android 1.0 + */ + public void setChoices(double[] limits, String[] formats) { + if (limits.length != formats.length) { + throw new IllegalArgumentException(); + } + choiceLimits = limits; + choiceFormats = formats; + } + + private int skipWhitespace(String string, int index) { + int length = string.length(); + while (index < length && Character.isWhitespace(string.charAt(index))) { + index++; + } + return index; + } + + /** + * Returns the pattern of this {@code ChoiceFormat} which specifies the + * ranges and their associated strings. + * + * @return the pattern. + * @since Android 1.0 + */ + public String toPattern() { + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < choiceLimits.length; i++) { + if (i != 0) { + buffer.append('|'); + } + String previous = String.valueOf(previousDouble(choiceLimits[i])); + String limit = String.valueOf(choiceLimits[i]); + if (previous.length() < limit.length()) { + buffer.append(previous); + buffer.append('<'); + } else { + buffer.append(limit); + buffer.append('#'); + } + boolean quote = (choiceFormats[i].indexOf('|') != -1); + if (quote) { + buffer.append('\''); + } + buffer.append(choiceFormats[i]); + if (quote) { + buffer.append('\''); + } + } + return buffer.toString(); + } +} diff --git a/text/src/main/java/java/text/CollationElementIterator.java b/text/src/main/java/java/text/CollationElementIterator.java new file mode 100644 index 0000000..66c0079 --- /dev/null +++ b/text/src/main/java/java/text/CollationElementIterator.java @@ -0,0 +1,251 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.text; + +// BEGIN android-note +// The icu implementation used was changed from icu4j to icu4jni. +// END android-note + +/** + * Created by a {@code RuleBasedCollator} to iterate through a string. The + * result of each iteration is a 32-bit collation element that defines the + * ordering priority of the next character or sequence of characters in the + * source string. + * <p> + * For illustration, consider the following in Spanish: + * </p> + * <p> + * "ca": the first collation element is collation_element('c') and second + * collation element is collation_element('a'). + * </p> + * <p> + * Since "ch" in Spanish sorts as one entity, the example below returns one + * collation element for the two characters 'c' and 'h': + * </p> + * <p> + * "cha": the first collation element is collation_element('ch') and the second + * one is collation_element('a'). + * </p> + * <p> + * In German, since the character '\u0086' is a composed character of 'a' + * and 'e', the iterator returns two collation elements for the single character + * '\u0086': + * </p> + * <p> + * "\u0086b": the first collation element is collation_element('a'), the + * second one is collation_element('e'), and the third collation element is + * collation_element('b'). + * </p> + * + * @since Android 1.0 + */ +public final class CollationElementIterator { + + /** + * This constant is returned by the iterator in the methods + * {@code next()} and {@code previous()} when the end or the + * beginning of the source string has been reached, and there are no more + * valid collation elements to return. + * + * @since Android 1.0 + */ + public static final int NULLORDER = -1; + + private com.ibm.icu4jni.text.CollationElementIterator icuIterator; + + CollationElementIterator(com.ibm.icu4jni.text.CollationElementIterator iterator) { + this.icuIterator = iterator; + } + + /** + * Obtains the maximum length of any expansion sequence that ends with the + * specified collation element. Returns {@code 1} if there is no expansion + * with this collation element as the last element. + * + * @param order + * a collation element that has been previously obtained from a + * call to either the {@link #next()} or {@link #previous()} + * method. + * @return the maximum length of any expansion sequence ending with the + * specified collation element. + * @since Android 1.0 + */ + public int getMaxExpansion(int order) { + return this.icuIterator.getMaxExpansion(order); + } + + /** + * Obtains the character offset in the source string corresponding to the + * next collation element. This value could be any of: + * <ul> + * <li>The index of the first character in the source string that matches + * the value of the next collation element. This means that if + * {@code setOffset(offset)} sets the index in the middle of a contraction, + * {@code getOffset()} returns the index of the first character in the + * contraction, which may not be equal to the original offset that was set. + * Hence calling {@code getOffset()} immediately after + * {@code setOffset(offset)} does not guarantee that the original offset set + * will be returned.</li> + * <li>If normalization is on, the index of the immediate subsequent + * character, or composite character with the first character, having a + * combining class of 0.</li> + * <li>The length of the source string, if iteration has reached the end. + * </li> + * </ul> + * + * @return The position of the collation element in the source string that + * will be returned by the next invocation of the {@link #next()} + * method. + * @since Android 1.0 + */ + public int getOffset() { + return this.icuIterator.getOffset(); + } + + /** + * Obtains the next collation element in the source string. + * + * @return the next collation element or {@code NULLORDER} if the end + * of the iteration has been reached. + * @since Android 1.0 + */ + public int next() { + return this.icuIterator.next(); + } + + /** + * Obtains the previous collation element in the source string. + * + * @return the previous collation element, or {@code NULLORDER} when + * the start of the iteration has been reached. + * @since Android 1.0 + */ + public int previous() { + return this.icuIterator.previous(); + } + + /** + * Obtains the primary order of the specified collation element, i.e. the + * first 16 bits. This value is unsigned. + * + * @param order + * the element of the collation. + * @return the element's 16 bit primary order. + * @since Android 1.0 + */ + public static final int primaryOrder(int order) { + return com.ibm.icu4jni.text.CollationElementIterator.primaryOrder(order); + } + + /** + * Repositions the cursor to point at the first element of the current + * string. The next call to {@link #next()} or {@link #previous()} will + * return the first and last collation element in the string, respectively. + * <p> + * If the {@code RuleBasedCollator} used by this iterator has had its + * attributes changed, calling {@code reset()} reinitializes the iterator to + * use the new attributes. + * </p> + * + * @since Android 1.0 + */ + public void reset() { + this.icuIterator.reset(); + } + + /** + * Obtains the secondary order of the specified collation element, i.e. the + * 16th to 23th bits, inclusive. This value is unsigned. + * + * @param order + * the element of the collator. + * @return the 8 bit secondary order of the element. + * @since Android 1.0 + */ + public static final short secondaryOrder(int order) { + return (short) com.ibm.icu4jni.text.CollationElementIterator + .secondaryOrder(order); + } + + /** + * Points the iterator at the collation element associated with the + * character in the source string which is found at the supplied offset. + * After this call completes, an invocation of the {@link #next()} method + * will return this collation element. + * <p> + * If {@code newOffset} corresponds to a character which is part of a + * sequence that maps to a single collation element then the iterator is + * adjusted to the start of that sequence. As a result of this, any + * subsequent call made to {@code getOffset()} may not return the same value + * set by this method. + * </p> + * <p> + * If the decomposition mode is on, and offset is in the middle of a + * decomposable range of source text, the iterator may not return a correct + * result for the next forwards or backwards iteration. The user must ensure + * that the offset is not in the middle of a decomposable range. + * </p> + * + * @param newOffset + * the character offset into the original source string to set. + * Note that this is not an offset into the corresponding + * sequence of collation elements. + * @since Android 1.0 + */ + public void setOffset(int newOffset) { + this.icuIterator.setOffset(newOffset); + } + + /** + * Sets a new source string iterator for iteration, and resets the offset to + * the beginning of the text. + * + * @param source + * the new source string iterator for iteration. + * @since Android 1.0 + */ + public void setText(CharacterIterator source) { + this.icuIterator.setText(source); + } + + /** + * Sets a new source string for iteration, and resets the offset to the + * beginning of the text. + * + * @param source + * the new source string for iteration. + * @since Android 1.0 + */ + public void setText(String source) { + this.icuIterator.setText(source); + } + + /** + * Obtains the tertiary order of the specified collation element, i.e. the + * last 8 bits. This value is unsigned. + * + * @param order + * the element of the collation. + * @return the 8 bit tertiary order of the element. + * @since Android 1.0 + */ + public static final short tertiaryOrder(int order) { + return (short) com.ibm.icu4jni.text.CollationElementIterator + .tertiaryOrder(order); + } +} diff --git a/text/src/main/java/java/text/CollationKey.java b/text/src/main/java/java/text/CollationKey.java new file mode 100644 index 0000000..f43bfd1 --- /dev/null +++ b/text/src/main/java/java/text/CollationKey.java @@ -0,0 +1,180 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** +******************************************************************************* +* Copyright (C) 1996-2006, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc and some of the method descriptions are copied from ICU4J +// source files. Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// The icu implementation used was changed from icu4j to icu4jni. +// END android-note + +package java.text; +/** + * Represents a string under the rules of a specific {@code Collator} object. + * Comparing two {@code CollationKey} instances returns the relative order of + * the strings they represent. + * <p> + * Since the rule set of collators can differ, the sort orders of the same + * string under two different {@code Collator} instances might differ. Hence + * comparing collation keys generated from different {@code Collator} instances + * can give incorrect results. + * </p> + * <p> + * Both the method {@code CollationKey.compareTo(CollationKey)} and the method + * {@code Collator.compare(String, String)} compares two strings and returns + * their relative order. The performance characteristics of these two approaches + * can differ. + * </p> + * <p> + * During the construction of a {@code CollationKey}, the entire source string + * is examined and processed into a series of bits terminated by a null, that + * are stored in the {@code CollationKey}. When + * {@code CollationKey.compareTo(CollationKey)} executes, it performs bitwise + * comparison on the bit sequences. This can incur startup cost when creating + * the {@code CollationKey}, but once the key is created, binary comparisons + * are fast. This approach is recommended when the same strings are to be + * compared over and over again. + * </p> + * <p> + * On the other hand, implementations of + * {@code Collator.compare(String, String)} can examine and process the strings + * only until the first characters differ in order. This approach is + * recommended if the strings are to be compared only once. + * </p> + * <p> + * The following example shows how collation keys can be used to sort a + * list of strings: + * </p> + * <blockquote> + * + * <pre> + * // Create an array of CollationKeys for the Strings to be sorted. + * Collator myCollator = Collator.getInstance(); + * CollationKey[] keys = new CollationKey[3]; + * keys[0] = myCollator.getCollationKey("Tom"); + * keys[1] = myCollator.getCollationKey("Dick"); + * keys[2] = myCollator.getCollationKey("Harry"); + * sort(keys); + * <br> + * //... + * <br> + * // Inside body of sort routine, compare keys this way + * if( keys[i].compareTo( keys[j] ) > 0 ) + * // swap keys[i] and keys[j] + * <br> + * //... + * <br> + * // Finally, when we've returned from sort. + * System.out.println(keys[0].getSourceString()); + * System.out.println(keys[1].getSourceString()); + * System.out.println(keys[2].getSourceString()); + * </pre> + * + * </blockquote> + * + * @see Collator + * @see RuleBasedCollator + * @since Android 1.0 + */ +public final class CollationKey implements Comparable<CollationKey> { + + private String source; + + private com.ibm.icu4jni.text.CollationKey icuKey; + + CollationKey(String source, com.ibm.icu4jni.text.CollationKey key) { + this.source = source; + this.icuKey = key; + } + + /** + * Compares this object to the specified collation key object to determine + * their relative order. + * + * @param value + * the collation key object to compare this object to. + * @return a negative value if this {@code CollationKey} is less than the + * specified {@code CollationKey}, 0 if they are equal and a + * positive value if this {@code CollationKey} is greater. + * @since Android 1.0 + */ + public int compareTo(CollationKey value) { + return icuKey.compareTo(value.icuKey); + } + + /** + * Compares the specified object to this {@code CollationKey} and indicates + * if they are equal. The object must be an instance of {@code CollationKey} + * and have the same source string and collation key. Both instances of + * {@code CollationKey} must have been created by the same {@code Collator}. + * + * @param object + * the object to compare to this object. + * @return {@code true} if {@code object} is equal to this collation key; + * {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof CollationKey)) { + return false; + } + CollationKey collationKey = (CollationKey) object; + return icuKey.equals(collationKey.icuKey); + } + + /** + * Returns the string from which this collation key was created. + * + * @return the source string of this collation key. + * @since Android 1.0 + */ + public String getSourceString() { + return this.source; + } + + /** + * Returns an integer hash code for the receiver. Objects which are equal + * return the same value for this method. + * + * @return the receiver's hash. + * + * @see #equals + * @since Android 1.0 + */ + @Override + public int hashCode() { + return icuKey.hashCode(); + } + + /** + * Returns the collation key as a byte array. + * + * @return an array of bytes. + * @since Android 1.0 + */ + public byte[] toByteArray() { + return icuKey.toByteArray(); + } +} diff --git a/text/src/main/java/java/text/Collator.java b/text/src/main/java/java/text/Collator.java new file mode 100644 index 0000000..a34b412 --- /dev/null +++ b/text/src/main/java/java/text/Collator.java @@ -0,0 +1,502 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** +******************************************************************************* +* Copyright (C) 1996-2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc and some of the method descriptions are copied from ICU4J +// source files. Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// The icu implementation used was changed from icu4j to icu4jni. +// END android-note + +package java.text; + +import java.security.AccessController; +import java.util.Comparator; +import java.util.Locale; +import java.util.Vector; + +import org.apache.harmony.luni.util.PriviAction; + +/** + * Performs locale-sensitive string comparison. A concrete subclass, + * {@link RuleBasedCollator}, allows customization of the collation ordering by + * the use of rule sets. + * <p> + * Following the <a href=http://www.unicode.org>Unicode Consortium</a>'s + * specifications for the <a + * href="http://www.unicode.org/unicode/reports/tr10/"> Unicode Collation + * Algorithm (UCA)</a>, there are 4 different levels of strength used in + * comparisons: + * </p> + * <ul> + * <li>PRIMARY strength: Typically, this is used to denote differences between + * base characters (for example, "a" < "b"). It is the strongest difference. + * For example, dictionaries are divided into different sections by base + * character. + * <li>SECONDARY strength: Accents in the characters are considered secondary + * differences (for example, "as" < "às" < "at"). Other differences + * between letters can also be considered secondary differences, depending on + * the language. A secondary difference is ignored when there is a primary + * difference anywhere in the strings. + * <li>TERTIARY strength: Upper and lower case differences in characters are + * distinguished at tertiary strength (for example, "ao" < "Ao" < + * "aò"). In addition, a variant of a letter differs from the base form + * on the tertiary strength (such as "A" and "Ⓐ"). Another example is the + * difference between large and small Kana. A tertiary difference is ignored + * when there is a primary or secondary difference anywhere in the strings. + * <li>IDENTICAL strength: When all other strengths are equal, the IDENTICAL + * strength is used as a tiebreaker. The Unicode code point values of the NFD + * form of each string are compared, just in case there is no difference. For + * example, Hebrew cantellation marks are only distinguished at this strength. + * This strength should be used sparingly, as only code point value differences + * between two strings are an extremely rare occurrence. Using this strength + * substantially decreases the performance for both comparison and collation key + * generation APIs. This strength also increases the size of the collation key. + * </ul> + * <p> + * This {@code Collator} deals only with two decomposition modes, the canonical + * decomposition mode and one that does not use any decomposition. The + * compatibility decomposition mode + * {@code java.text.Collator.FULL_DECOMPOSITION} is not supported here. If the + * canonical decomposition mode is set, {@code Collator} handles un-normalized + * text properly, producing the same results as if the text were normalized in + * NFD. If canonical decomposition is turned off, it is the user's + * responsibility to ensure that all text is already in the appropriate form + * before performing a comparison or before getting a {@link CollationKey}. + * </p> + * <p> + * <em>Examples:</em> + * </p> + * <blockquote> + * + * <pre> + * // Get the Collator for US English and set its strength to PRIMARY + * Collator usCollator = Collator.getInstance(Locale.US); + * usCollator.setStrength(Collator.PRIMARY); + * if (usCollator.compare("abc", "ABC") == 0) { + * System.out.println("Strings are equivalent"); + * } + * </pre> + * + * </blockquote> + * <p> + * The following example shows how to compare two strings using the collator for + * the default locale. + * </p> + * <blockquote> + * + * <pre> + * // Compare two strings in the default locale + * Collator myCollator = Collator.getInstance(); + * myCollator.setDecomposition(Collator.NO_DECOMPOSITION); + * if (myCollator.compare("\u00e0\u0325", "a\u0325\u0300") != 0) { + * System.out.println("\u00e0\u0325 is not equal to a\u0325\u0300 without decomposition"); + * myCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION); + * if (myCollator.compare("\u00e0\u0325", "a\u0325\u0300") != 0) { + * System.out.println("Error: \u00e0\u0325 should be equal to a\u0325\u0300 with decomposition"); + * } else { + * System.out.println("\u00e0\u0325 is equal to a\u0325\u0300 with decomposition"); + * } + * } else { + * System.out.println("Error: \u00e0\u0325 should be not equal to a\u0325\u0300 without decomposition"); + * } + * </pre> + * + * </blockquote> + * + * @see RuleBasedCollator + * @see CollationKey + * @since Android 1.0 + */ +public abstract class Collator implements Comparator<Object>, Cloneable { + + static final int EQUAL = 0; + + static final int GREATER = 1; + + static final int LESS = -1; + + /** + * Constant used to specify the decomposition rule. + * + * @since Android 1.0 + */ + public static final int NO_DECOMPOSITION = 0; + + /** + * Constant used to specify the decomposition rule. + * + * @since Android 1.0 + */ + public static final int CANONICAL_DECOMPOSITION = 1; + + /** + * Constant used to specify the decomposition rule. This value for + * decomposition is not supported. + * + * @since Android 1.0 + */ + public static final int FULL_DECOMPOSITION = 2; + + /** + * Constant used to specify the collation strength. + * + * @since Android 1.0 + */ + public static final int PRIMARY = 0; + + /** + * Constant used to specify the collation strength. + * + * @since Android 1.0 + */ + public static final int SECONDARY = 1; + + /** + * Constant used to specify the collation strength. + * + * @since Android 1.0 + */ + public static final int TERTIARY = 2; + + /** + * Constant used to specify the collation strength. + * + * @since Android 1.0 + */ + public static final int IDENTICAL = 3; + + private static int CACHE_SIZE; + + static { + // CACHE_SIZE includes key and value, so needs to be double + String cacheSize = AccessController + .doPrivileged(new PriviAction<String>("collator.cache")); //$NON-NLS-1$ + if (cacheSize != null) { + try { + CACHE_SIZE = Integer.parseInt(cacheSize); + } catch (NumberFormatException e) { + CACHE_SIZE = 6; + } + } else { + CACHE_SIZE = 6; + } + } + + private static Vector<Collator> cache = new Vector<Collator>(CACHE_SIZE); + + // Wrapper class of ICU4JNI Collator + com.ibm.icu4jni.text.Collator icuColl; + + Collator(com.ibm.icu4jni.text.Collator wrapper) { + this.icuColl = wrapper; + } + + /** + * Constructs a new {@code Collator} instance. + * + * @since Android 1.0 + */ + protected Collator() { + super(); + // BEGIN android-added + icuColl = com.ibm.icu4jni.text.Collator.getInstance(Locale.getDefault()); + // END android-added + } + + /** + * Returns a new collator with the same decomposition mode and + * strength value as this collator. + * + * @return a shallow copy of this collator. + * @see java.lang.Cloneable + * @since Android 1.0 + */ + @Override + public Object clone() { + try { + Collator clone = (Collator) super.clone(); + clone.icuColl = (com.ibm.icu4jni.text.Collator) this.icuColl.clone(); + return clone; + } catch (CloneNotSupportedException e) { + return null; + } + } + + /** + * Compares two objects to determine their relative order. The objects must + * be strings. + * + * @param object1 + * the first string to compare. + * @param object2 + * the second string to compare. + * @return a negative value if {@code object1} is less than {@code object2}, + * 0 if they are equal, and a positive value if {@code object1} is + * greater than {@code object2}. + * @exception ClassCastException + * if {@code object1} or {@code object2} is not a + * {@code String}. + * @since Android 1.0 + */ + public int compare(Object object1, Object object2) { + return compare((String) object1, (String) object2); + } + + /** + * Compares two strings to determine their relative order. + * + * @param string1 + * the first string to compare. + * @param string2 + * the second string to compare. + * @return a negative value if {@code string1} is less than {@code string2}, + * 0 if they are equal and a positive value if {@code string1} is + * greater than {@code string2}. + * @since Android 1.0 + */ + public abstract int compare(String string1, String string2); + + /** + * Compares this collator with the specified object and indicates if they + * are equal. + * + * @param object + * the object to compare with this object. + * @return {@code true} if {@code object} is a {@code Collator} object and + * it has the same strength and decomposition values as this + * collator; {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof Collator)) { + return false; + } + Collator collator = (Collator) object; + return this.icuColl == null ? collator.icuColl == null : this.icuColl + .equals(collator.icuColl); + } + + /** + * Compares two strings using the collation rules to determine if they are + * equal. + * + * @param string1 + * the first string to compare. + * @param string2 + * the second string to compare. + * @return {@code true} if {@code string1} and {@code string2} are equal + * using the collation rules, false otherwise. + * @since Android 1.0 + */ + public boolean equals(String string1, String string2) { + return compare(string1, string2) == 0; + } + + /** + * Gets the list of installed {@link java.util.Locale} objects which support + * {@code Collator}. + * + * @return an array of {@code Locale}. + * @since Android 1.0 + */ + public static Locale[] getAvailableLocales() { + return com.ibm.icu4jni.text.Collator.getAvailableLocales(); + } + + /** + * Returns a {@link CollationKey} for the specified string for this collator + * with the current decomposition rule and strength value. + * + * @param string + * the source string that is converted into a collation key. + * @return the collation key for {@code string}. + * @since Android 1.0 + */ + public abstract CollationKey getCollationKey(String string); + + /** + * Returns the decomposition rule for this collator. + * + * @return the decomposition rule, either {@code NO_DECOMPOSITION} or + * {@code CANONICAL_DECOMPOSITION}. {@code FULL_DECOMPOSITION} is + * not supported. + * @since Android 1.0 + */ + public int getDecomposition() { + return decompositionMode_ICU_Java(this.icuColl.getDecomposition()); + } + + /** + * Returns a {@code Collator} instance which is appropriate for the default + * {@code Locale}. + * + * @return the collator for the default locale. + * @since Android 1.0 + */ + public static Collator getInstance() { + return getInstance(Locale.getDefault()); + } + + /** + * Returns a {@code Collator} instance which is appropriate for the + * specified {@code Locale}. + * + * @param locale + * the locale. + * @return the collator for {@code locale}. + * @since Android 1.0 + */ + public static Collator getInstance(Locale locale) { + String key = locale.toString(); + for (int i = cache.size() - 1; i >= 0; i -= 2) { + if (cache.elementAt(i).equals(key)) { + return (Collator) (cache.elementAt(i - 1)).clone(); + } + } + + return new RuleBasedCollator(com.ibm.icu4jni.text.Collator + .getInstance(locale)); + } + + /** + * Returns the strength value for this collator. + * + * @return the strength value, either PRIMARY, SECONDARY, TERTIARY or + * IDENTICAL. + * @since Android 1.0 + */ + public int getStrength() { + return strength_ICU_Java(this.icuColl.getStrength()); + } + + /** + * Returns an integer hash code for this collator. + * + * @return this collator's hash code. + * + * @see #equals(Object) + * @see #equals(String, String) + * @since Android 1.0 + */ + @Override + public abstract int hashCode(); + + /** + * Sets the decomposition rule for this collator. + * + * @param value + * the decomposition rule, either {@code NO_DECOMPOSITION} or + * {@code CANONICAL_DECOMPOSITION}. {@code FULL_DECOMPOSITION} + * is not supported. + * @exception IllegalArgumentException + * if the provided decomposition rule is not valid. This + * includes {@code FULL_DECOMPOSITION}. + * @since Android 1.0 + */ + public void setDecomposition(int value) { + this.icuColl.setDecomposition(decompositionMode_Java_ICU(value)); + } + + /** + * Sets the strength value for this collator. + * + * @param value + * the strength value, either PRIMARY, SECONDARY, TERTIARY, or + * IDENTICAL. + * + * @exception IllegalArgumentException + * if the provided strength value is not valid. + * @since Android 1.0 + */ + public void setStrength(int value) { + this.icuColl.setStrength(strength_Java_ICU(value)); + } + + private int decompositionMode_Java_ICU(int mode) { + int icuDecomp = mode; + switch (mode) { + case Collator.CANONICAL_DECOMPOSITION: + icuDecomp = com.ibm.icu4jni.text.Collator.CANONICAL_DECOMPOSITION; + break; + case Collator.NO_DECOMPOSITION: + icuDecomp = com.ibm.icu4jni.text.Collator.NO_DECOMPOSITION; + break; + } + return icuDecomp; + } + + private int decompositionMode_ICU_Java(int mode) { + int javaMode = mode; + switch (mode) { + case com.ibm.icu4jni.text.Collator.NO_DECOMPOSITION: + javaMode = Collator.NO_DECOMPOSITION; + break; + case com.ibm.icu4jni.text.Collator.CANONICAL_DECOMPOSITION: + javaMode = Collator.CANONICAL_DECOMPOSITION; + break; + } + return javaMode; + } + + private int strength_Java_ICU(int value) { + int icuValue = value; + switch (value) { + case Collator.PRIMARY: + icuValue = com.ibm.icu4jni.text.Collator.PRIMARY; + break; + case Collator.SECONDARY: + icuValue = com.ibm.icu4jni.text.Collator.SECONDARY; + break; + case Collator.TERTIARY: + icuValue = com.ibm.icu4jni.text.Collator.TERTIARY; + break; + case Collator.IDENTICAL: + icuValue = com.ibm.icu4jni.text.Collator.IDENTICAL; + break; + } + return icuValue; + + } + + private int strength_ICU_Java(int value) { + int javaValue = value; + switch (value) { + case com.ibm.icu4jni.text.Collator.PRIMARY: + javaValue = Collator.PRIMARY; + break; + case com.ibm.icu4jni.text.Collator.SECONDARY: + javaValue = Collator.SECONDARY; + break; + case com.ibm.icu4jni.text.Collator.TERTIARY: + javaValue = Collator.TERTIARY; + break; + case com.ibm.icu4jni.text.Collator.IDENTICAL: + javaValue = Collator.IDENTICAL; + break; + } + return javaValue; + } +} diff --git a/text/src/main/java/java/text/DateFormat.java b/text/src/main/java/java/text/DateFormat.java new file mode 100644 index 0000000..38759ae --- /dev/null +++ b/text/src/main/java/java/text/DateFormat.java @@ -0,0 +1,1127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** +******************************************************************************* +* Copyright (C) 1996-2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc and some of the method descriptions are copied from ICU4J +// source files. Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// END android-note + +package java.text; + +import java.io.InvalidObjectException; +import java.util.Calendar; +import java.util.Date; +import java.util.Hashtable; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.TimeZone; + +import org.apache.harmony.text.internal.nls.Messages; + +/** + * An abstract class for date/time formatting subclasses which formats and + * parses dates or time in a language-independent manner. The date/time + * formatting subclass, such as {@link SimpleDateFormat}, allows for formatting + * (i.e., date -> text), parsing (text -> date), and normalization. The date is + * represented as a {@code Date} object or as the milliseconds since January 1, + * 1970, 00:00:00 GMT. + * <p> + * DateFormat provides many class methods for obtaining default date/time + * formatters based on the default or a given locale and a number of formatting + * styles. The formatting styles include FULL, LONG, MEDIUM, and SHORT. More + * details and examples for using these styles are provided in the method + * descriptions. + * </p> + * <p> + * {@code DateFormat} helps you to format and parse dates for any locale. Your + * code can be completely independent of the locale conventions for months, days + * of the week, or even the calendar format: lunar vs. solar. + * </p> + * <p> + * To format a date for the current Locale, use one of the static factory + * methods: + * </p> + * <blockquote> + * + * <pre> + * myString = DateFormat.getDateInstance().format(myDate); + * </pre> + * + * </blockquote> + * <p> + * If you are formatting multiple dates, it is more efficient to get the format + * and use it multiple times so that the system doesn't have to fetch the + * information about the local language and country conventions multiple times. + * </p> + * <blockquote> + * + * <pre> + * DateFormat df = DateFormat.getDateInstance(); + * for (int i = 0; i < a.length; ++i) { + * output.println(df.format(myDate[i]) + "; "); + * } + * </pre> + * + * </blockquote> + * <p> + * To format a number for a different locale, specify it in the call to + * {@code getDateInstance}: + * </p> + * <blockquote> + * + * <pre> + * DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE); + * </pre> + * + * </blockquote> + * <p> + * {@code DateFormat} can also be used to parse strings: + * </p> + * <blockquote> + * + * <pre> + * myDate = df.parse(myString); + * </pre> + * + * </blockquote> + * <p> + * Use {@code getDateInstance} to get the normal date format for a country. + * Other static factory methods are available: Use {@code getTimeInstance} to + * get the time format for a country. Use {@code getDateTimeInstance} to get the + * date and time format. You can pass in different options to these factory + * methods to control the length of the result; from SHORT to MEDIUM to LONG to + * FULL. The exact result depends on the locale, but generally: + * </p> + * <ul> + * <li>SHORT is completely numeric, such as 12.13.52 or 3:30pm + * <li>MEDIUM is longer, such as Jan 12, 1952 + * <li>LONG is longer, such as January 12, 1952 or 3:30:32pm + * <li>FULL is pretty completely specified, such as Tuesday, April 12, 1952 AD + * or 3:30:42pm PST. + * </ul> + * <p> + * If needed, the time zone can be set on the format. For even greater control + * over the formatting or parsing, try casting the {@code DateFormat} you get + * from the factory methods to a {@code SimpleDateFormat}. This will work for + * the majority of countries; just remember to put it in a try block in case you + * encounter an unusual one. + * </p> + * <p> + * There are versions of the parse and format methods which use + * {@code ParsePosition} and {@code FieldPosition} to allow you to + * <ul> + * <li>progressively parse through pieces of a string; + * <li>align any particular field. + * </ul> + * <h4>Synchronization</h4> + * <p> + * Date formats are not synchronized. It is recommended to create separate + * format instances for each thread. If multiple threads access a format + * concurrently, it must be synchronized externally. + * </p> + * + * @see NumberFormat + * @see SimpleDateFormat + * @see Calendar + * @see TimeZone + * @since Android 1.0 + */ +public abstract class DateFormat extends Format { + + private static final long serialVersionUID = 7218322306649953788L; + + /** + * The calendar that this {@code DateFormat} uses to format a number + * representing a date. + * + * @since Android 1.0 + */ + protected Calendar calendar; + + /** + * The number format used to format a number. + * + * @since Android 1.0 + */ + protected NumberFormat numberFormat; + + /** + * The format style constant defining the default format style. The default + * is MEDIUM. + * + * @since Android 1.0 + */ + public final static int DEFAULT = 2; + + /** + * The format style constant defining the full style. + * + * @since Android 1.0 + */ + public final static int FULL = 0; + + /** + * The format style constant defining the long style. + * + * @since Android 1.0 + */ + public final static int LONG = 1; + + /** + * The format style constant defining the medium style. + * + * @since Android 1.0 + */ + public final static int MEDIUM = 2; + + /** + * The format style constant defining the short style. + * + * @since Android 1.0 + */ + public final static int SHORT = 3; + + /** + * The {@code FieldPosition} selector for 'G' field alignment, corresponds + * to the {@link Calendar#ERA} field. + * + * @since Android 1.0 + */ + public final static int ERA_FIELD = 0; + + /** + * The {@code FieldPosition} selector for 'y' field alignment, corresponds + * to the {@link Calendar#YEAR} field. + * + * @since Android 1.0 + */ + public final static int YEAR_FIELD = 1; + + /** + * The {@code FieldPosition} selector for 'M' field alignment, corresponds + * to the {@link Calendar#MONTH} field. + * + * @since Android 1.0 + */ + public final static int MONTH_FIELD = 2; + + /** + * The {@code FieldPosition} selector for 'd' field alignment, corresponds + * to the {@link Calendar#DATE} field. + * + * @since Android 1.0 + */ + public final static int DATE_FIELD = 3; + + /** + * The {@code FieldPosition} selector for 'k' field alignment, corresponds + * to the {@link Calendar#HOUR_OF_DAY} field. {@code HOUR_OF_DAY1_FIELD} is + * used for the one-based 24-hour clock. For example, 23:59 + 01:00 results + * in 24:59. + * + * @since Android 1.0 + */ + public final static int HOUR_OF_DAY1_FIELD = 4; + + /** + * The {@code FieldPosition} selector for 'H' field alignment, corresponds + * to the {@link Calendar#HOUR_OF_DAY} field. {@code HOUR_OF_DAY0_FIELD} is + * used for the zero-based 24-hour clock. For example, 23:59 + 01:00 results + * in 00:59. + * + * @since Android 1.0 + */ + public final static int HOUR_OF_DAY0_FIELD = 5; + + /** + * FieldPosition selector for 'm' field alignment, corresponds to the + * {@link Calendar#MINUTE} field. + * + * @since Android 1.0 + */ + public final static int MINUTE_FIELD = 6; + + /** + * FieldPosition selector for 's' field alignment, corresponds to the + * {@link Calendar#SECOND} field. + * + * @since Android 1.0 + */ + public final static int SECOND_FIELD = 7; + + /** + * FieldPosition selector for 'S' field alignment, corresponds to the + * {@link Calendar#MILLISECOND} field. + * + * @since Android 1.0 + */ + public final static int MILLISECOND_FIELD = 8; + + /** + * FieldPosition selector for 'E' field alignment, corresponds to the + * {@link Calendar#DAY_OF_WEEK} field. + * + * @since Android 1.0 + */ + public final static int DAY_OF_WEEK_FIELD = 9; + + /** + * FieldPosition selector for 'D' field alignment, corresponds to the + * {@link Calendar#DAY_OF_YEAR} field. + * + * @since Android 1.0 + */ + public final static int DAY_OF_YEAR_FIELD = 10; + + /** + * FieldPosition selector for 'F' field alignment, corresponds to the + * {@link Calendar#DAY_OF_WEEK_IN_MONTH} field. + * + * @since Android 1.0 + */ + public final static int DAY_OF_WEEK_IN_MONTH_FIELD = 11; + + /** + * FieldPosition selector for 'w' field alignment, corresponds to the + * {@link Calendar#WEEK_OF_YEAR} field. + * + * @since Android 1.0 + */ + public final static int WEEK_OF_YEAR_FIELD = 12; + + /** + * FieldPosition selector for 'W' field alignment, corresponds to the + * {@link Calendar#WEEK_OF_MONTH} field. + * + * @since Android 1.0 + */ + public final static int WEEK_OF_MONTH_FIELD = 13; + + /** + * FieldPosition selector for 'a' field alignment, corresponds to the + * {@link Calendar#AM_PM} field. + * + * @since Android 1.0 + */ + public final static int AM_PM_FIELD = 14; + + /** + * FieldPosition selector for 'h' field alignment, corresponding to the + * {@link Calendar#HOUR} field. {@code HOUR1_FIELD} is used for the + * one-based 12-hour clock. For example, 11:30 PM + 1 hour results in 12:30 + * AM. + * + * @since Android 1.0 + */ + public final static int HOUR1_FIELD = 15; + + /** + * The {@code FieldPosition} selector for 'z' field alignment, corresponds + * to the {@link Calendar#ZONE_OFFSET} and {@link Calendar#DST_OFFSET} + * fields. + * + * @since Android 1.0 + */ + public final static int HOUR0_FIELD = 16; + + /** + * The {@code FieldPosition} selector for 'z' field alignment, corresponds + * to the {@link Calendar#ZONE_OFFSET} and {@link Calendar#DST_OFFSET} + * fields. + * + * @since Android 1.0 + */ + public final static int TIMEZONE_FIELD = 17; + + /** + * Constructs a new instance of {@code DateFormat}. + * + * @since Android 1.0 + */ + protected DateFormat() { + } + + /** + * Returns a new instance of {@code DateFormat} with the same properties. + * + * @return a shallow copy of this {@code DateFormat}. + * + * @see java.lang.Cloneable + * @since Android 1.0 + */ + @Override + public Object clone() { + DateFormat clone = (DateFormat) super.clone(); + clone.calendar = (Calendar) calendar.clone(); + clone.numberFormat = (NumberFormat) numberFormat.clone(); + return clone; + } + + /** + * Compares this date format with the specified object and indicates if they + * are equal. + * + * @param object + * the object to compare with this date format. + * @return {@code true} if {@code object} is a {@code DateFormat} object and + * it has the same properties as this date format; {@code false} + * otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof DateFormat)) { + return false; + } + DateFormat dateFormat = (DateFormat) object; + return numberFormat.equals(dateFormat.numberFormat) + && calendar.getTimeZone().equals( + dateFormat.calendar.getTimeZone()) + && calendar.getFirstDayOfWeek() == dateFormat.calendar + .getFirstDayOfWeek() + && calendar.getMinimalDaysInFirstWeek() == dateFormat.calendar + .getMinimalDaysInFirstWeek() + && calendar.isLenient() == dateFormat.calendar.isLenient(); + } + + /** + * Formats the specified object as a string using the pattern of this date + * format and appends the string to the specified string buffer. + * <p> + * If the {@code field} member of {@code field} contains a value specifying + * a format field, then its {@code beginIndex} and {@code endIndex} members + * will be updated with the position of the first occurrence of this field + * in the formatted text. + * </p> + * + * @param object + * the source object to format, must be a {@code Date} or a + * {@code Number}. If {@code object} is a number then a date is + * constructed using the {@code longValue()} of the number. + * @param buffer + * the target string buffer to append the formatted date/time to. + * @param field + * on input: an optional alignment field; on output: the offsets + * of the alignment field in the formatted text. + * @return the string buffer. + * @exception IllegalArgumentException + * if {@code object} is neither a {@code Date} nor a + * {@code Number} instance. + * @since Android 1.0 + */ + @Override + public final StringBuffer format(Object object, StringBuffer buffer, + FieldPosition field) { + if (object instanceof Date) { + return format((Date) object, buffer, field); + } + if (object instanceof Number) { + return format(new Date(((Number) object).longValue()), buffer, + field); + } + throw new IllegalArgumentException(); + } + + /** + * Formats the specified date using the rules of this date format. + * + * @param date + * the date to format. + * @return the formatted string. + * @since Android 1.0 + */ + public final String format(Date date) { + return format(date, new StringBuffer(), new FieldPosition(0)) + .toString(); + } + + /** + * Formats the specified date as a string using the pattern of this date + * format and appends the string to the specified string buffer. + * <p> + * If the {@code field} member of {@code field} contains a value specifying + * a format field, then its {@code beginIndex} and {@code endIndex} members + * will be updated with the position of the first occurrence of this field + * in the formatted text. + * </p> + * + * @param date + * the date to format. + * @param buffer + * the target string buffer to append the formatted date/time to. + * @param field + * on input: an optional alignment field; on output: the offsets + * of the alignment field in the formatted text. + * @return the string buffer. + * @since Android 1.0 + */ + public abstract StringBuffer format(Date date, StringBuffer buffer, + FieldPosition field); + + /** + * Gets the list of installed locales which support {@code DateFormat}. + * + * @return an array of locales. + * @since Android 1.0 + */ + public static Locale[] getAvailableLocales() { + return Locale.getAvailableLocales(); + } + + /** + * Returns the calendar used by this {@code DateFormat}. + * + * @return the calendar used by this date format. + * @since Android 1.0 + */ + public Calendar getCalendar() { + return calendar; + } + + /** + * Returns a {@code DateFormat} instance for formatting and parsing dates in + * the DEFAULT style for the default locale. + * + * @return the {@code DateFormat} instance for the default style and locale. + * @since Android 1.0 + */ + public final static DateFormat getDateInstance() { + return getDateInstance(DEFAULT); + } + + /** + * Returns a {@code DateFormat} instance for formatting and parsing dates in + * the specified style for the default locale. + * + * @param style + * one of SHORT, MEDIUM, LONG, FULL, or DEFAULT. + * @return the {@code DateFormat} instance for {@code style} and the default + * locale. + * @throws IllegalArgumentException + * if {@code style} is not one of SHORT, MEDIUM, LONG, FULL, or + * DEFAULT. + * @since Android 1.0 + */ + public final static DateFormat getDateInstance(int style) { + checkDateStyle(style); + return getDateInstance(style, Locale.getDefault()); + } + + /** + * Returns a {@code DateFormat} instance for formatting and parsing dates in + * the specified style for the specified locale. + * + * @param style + * one of SHORT, MEDIUM, LONG, FULL, or DEFAULT. + * @param locale + * the locale. + * @throws IllegalArgumentException + * if {@code style} is not one of SHORT, MEDIUM, LONG, FULL, or + * DEFAULT. + * @return the {@code DateFormat} instance for {@code style} and + * {@code locale}. + * @since Android 1.0 + */ + public final static DateFormat getDateInstance(int style, Locale locale) { + checkDateStyle(style); + ResourceBundle bundle = getBundle(locale); + String pattern = bundle.getString("Date_" + getStyleName(style)); //$NON-NLS-1$ + return new SimpleDateFormat(pattern, locale); + } + + /** + * Returns a {@code DateFormat} instance for formatting and parsing dates + * and time values in the DEFAULT style for the default locale. + * + * @return the {@code DateFormat} instance for the default style and locale. + * @since Android 1.0 + */ + public final static DateFormat getDateTimeInstance() { + return getDateTimeInstance(DEFAULT, DEFAULT); + } + + /** + * Returns a {@code DateFormat} instance for formatting and parsing of both + * dates and time values in the manner appropriate for the default locale. + * + * @param dateStyle + * one of SHORT, MEDIUM, LONG, FULL, or DEFAULT. + * @param timeStyle + * one of SHORT, MEDIUM, LONG, FULL, or DEFAULT. + * @return the {@code DateFormat} instance for {@code dateStyle}, + * {@code timeStyle} and the default locale. + * @throws IllegalArgumentException + * if {@code dateStyle} or {@code timeStyle} is not one of + * SHORT, MEDIUM, LONG, FULL, or DEFAULT. + * @since Android 1.0 + */ + public final static DateFormat getDateTimeInstance(int dateStyle, + int timeStyle) { + checkTimeStyle(timeStyle); + checkDateStyle(dateStyle); + return getDateTimeInstance(dateStyle, timeStyle, Locale.getDefault()); + } + + /** + * Returns a {@code DateFormat} instance for formatting and parsing dates + * and time values in the specified styles for the specified locale. + * + * @param dateStyle + * one of SHORT, MEDIUM, LONG, FULL, or DEFAULT. + * @param timeStyle + * one of SHORT, MEDIUM, LONG, FULL, or DEFAULT. + * @param locale + * the locale. + * @return the {@code DateFormat} instance for {@code dateStyle}, + * {@code timeStyle} and {@code locale}. + * @throws IllegalArgumentException + * if {@code dateStyle} or {@code timeStyle} is not one of + * SHORT, MEDIUM, LONG, FULL, or DEFAULT. + * @since Android 1.0 + */ + public final static DateFormat getDateTimeInstance(int dateStyle, + int timeStyle, Locale locale) { + checkTimeStyle(timeStyle); + checkDateStyle(dateStyle); + ResourceBundle bundle = getBundle(locale); + String pattern = bundle.getString("Date_" + getStyleName(dateStyle)) //$NON-NLS-1$ + + " " + bundle.getString("Time_" + getStyleName(timeStyle)); //$NON-NLS-1$ //$NON-NLS-2$ + return new SimpleDateFormat(pattern, locale); + } + + /** + * Returns a {@code DateFormat} instance for formatting and parsing dates + * and times in the SHORT style for the default locale. + * + * @return the {@code DateFormat} instance for the SHORT style and default + * locale. + * @since Android 1.0 + */ + public final static DateFormat getInstance() { + return getDateTimeInstance(SHORT, SHORT); + } + + /** + * Returns the {@code NumberFormat} used by this {@code DateFormat}. + * + * @return the {@code NumberFormat} used by this date format. + * @since Android 1.0 + */ + public NumberFormat getNumberFormat() { + return numberFormat; + } + + static String getStyleName(int style) { + String styleName; + switch (style) { + case SHORT: + styleName = "SHORT"; //$NON-NLS-1$ + break; + case MEDIUM: + styleName = "MEDIUM"; //$NON-NLS-1$ + break; + case LONG: + styleName = "LONG"; //$NON-NLS-1$ + break; + case FULL: + styleName = "FULL"; //$NON-NLS-1$ + break; + default: + styleName = ""; //$NON-NLS-1$ + } + return styleName; + } + + /** + * Returns a {@code DateFormat} instance for formatting and parsing time + * values in the DEFAULT style for the default locale. + * + * @return the {@code DateFormat} instance for the default style and locale. + * @since Android 1.0 + */ + public final static DateFormat getTimeInstance() { + return getTimeInstance(DEFAULT); + } + + /** + * Returns a {@code DateFormat} instance for formatting and parsing time + * values in the specified style for the default locale. + * + * @param style + * one of SHORT, MEDIUM, LONG, FULL, or DEFAULT. + * @return the {@code DateFormat} instance for {@code style} and the default + * locale. + * @throws IllegalArgumentException + * if {@code style} is not one of SHORT, MEDIUM, LONG, FULL, or + * DEFAULT. + * @since Android 1.0 + */ + public final static DateFormat getTimeInstance(int style) { + checkTimeStyle(style); + return getTimeInstance(style, Locale.getDefault()); + } + + /** + * Returns a {@code DateFormat} instance for formatting and parsing time + * values in the specified style for the specified locale. + * + * @param style + * one of SHORT, MEDIUM, LONG, FULL, or DEFAULT. + * @param locale + * the locale. + * @throws IllegalArgumentException + * if {@code style} is not one of SHORT, MEDIUM, LONG, FULL, or + * DEFAULT. + * @return the {@code DateFormat} instance for {@code style} and + * {@code locale}. + * @since Android 1.0 + */ + public final static DateFormat getTimeInstance(int style, Locale locale) { + checkTimeStyle(style); + ResourceBundle bundle = getBundle(locale); + String pattern = bundle.getString("Time_" + getStyleName(style)); //$NON-NLS-1$ + return new SimpleDateFormat(pattern, locale); + } + + /** + * Returns the time zone of this date format's calendar. + * + * @return the time zone of the calendar used by this date format. + * @since Android 1.0 + */ + public TimeZone getTimeZone() { + return calendar.getTimeZone(); + } + + @Override + public int hashCode() { + return calendar.getFirstDayOfWeek() + + calendar.getMinimalDaysInFirstWeek() + + calendar.getTimeZone().hashCode() + + (calendar.isLenient() ? 1231 : 1237) + + numberFormat.hashCode(); + } + + /** + * Indicates whether the calendar used by this date format is lenient. + * + * @return {@code true} if the calendar is lenient; {@code false} otherwise. + * @since Android 1.0 + */ + public boolean isLenient() { + return calendar.isLenient(); + } + + /** + * Parses a date from the specified string using the rules of this date + * format. + * + * @param string + * the string to parse. + * @return the {@code Date} resulting from the parsing. + * @exception ParseException + * if an error occurs during parsing. + * @since Android 1.0 + */ + public Date parse(String string) throws ParseException { + ParsePosition position = new ParsePosition(0); + Date date = parse(string, position); + if (position.getErrorIndex() != -1 || position.getIndex() == 0) { + // text.19=Unparseable date: {0} + throw new ParseException( + Messages.getString("text.19", string), position.getErrorIndex()); //$NON-NLS-1$ + } + return date; + } + + /** + * Parses a date from the specified string starting at the index specified + * by {@code position}. If the string is successfully parsed then the index + * of the {@code ParsePosition} is updated to the index following the parsed + * text. On error, the index is unchanged and the error index of {@code + * ParsePosition} is set to the index where the error occurred. + * <p> + * By default, parsing is lenient: If the input is not in the form used by + * this object's format method but can still be parsed as a date, then the + * parse succeeds. Clients may insist on strict adherence to the format by + * calling {@code setLenient(false)}. + * </p> + * + * @param string + * the string to parse. + * @param position + * input/output parameter, specifies the start index in {@code + * string} from where to start parsing. If parsing is successful, + * it is updated with the index following the parsed text; on + * error, the index is unchanged and the error index is set to + * the index where the error occurred. + * @return the date resulting from the parse, or {@code null} if there is an + * error. + * @since Android 1.0 + */ + public abstract Date parse(String string, ParsePosition position); + + /** + * Parses a date from the specified string starting at the index specified + * by {@code position}. If the string is successfully parsed then the index + * of the {@code ParsePosition} is updated to the index following the parsed + * text. On error, the index is unchanged and the error index of + * {@code ParsePosition} is set to the index where the error occurred. + * <p> + * By default, parsing is lenient: If the input is not in the form used by + * this object's format method but can still be parsed as a date, then the + * parse succeeds. Clients may insist on strict adherence to the format by + * calling {@code setLenient(false)}. + * </p> + * + * @param string + * the string to parse. + * @param position + * input/output parameter, specifies the start index in + * {@code string} from where to start parsing. If parsing is + * successful, it is updated with the index following the parsed + * text; on error, the index is unchanged and the error index + * is set to the index where the error occurred. + * @return the date resulting from the parsing, or {@code null} if there is + * an error. + * @since Android 1.0 + */ + @Override + public Object parseObject(String string, ParsePosition position) { + return parse(string, position); + } + + /** + * Sets the calendar used by this date format. + * + * @param cal + * the new calendar. + * @since Android 1.0 + */ + public void setCalendar(Calendar cal) { + calendar = cal; + } + + /** + * Specifies whether or not date/time parsing shall be lenient. With lenient + * parsing, the parser may use heuristics to interpret inputs that do not + * precisely match this object's format. With strict parsing, inputs must + * match this object's format. + * + * @param value + * {@code true} to set the calendar to be lenient, {@code false} + * otherwise. + * @since Android 1.0 + */ + public void setLenient(boolean value) { + calendar.setLenient(value); + } + + /** + * Sets the {@code NumberFormat} used by this date format. + * + * @param format + * the new number format. + * @since Android 1.0 + */ + public void setNumberFormat(NumberFormat format) { + numberFormat = format; + } + + /** + * Sets the time zone of the calendar used by this date format. + * + * @param timezone + * the new time zone. + * @since Android 1.0 + */ + public void setTimeZone(TimeZone timezone) { + calendar.setTimeZone(timezone); + } + + /** + * The instances of this inner class are used as attribute keys and values + * in {@code AttributedCharacterIterator} that the + * {@link SimpleDateFormat#formatToCharacterIterator(Object)} method returns. + * <p> + * There is no public constructor in this class, the only instances are the + * constants defined here. + * </p> + * @since Android 1.0 + */ + public static class Field extends Format.Field { + + private static final long serialVersionUID = 7441350119349544720L; + + private static Hashtable<Integer, Field> table = new Hashtable<Integer, Field>(); + + /** + * Marks the era part of a date. + * + * @since Android 1.0 + */ + public final static Field ERA = new Field("era", Calendar.ERA); //$NON-NLS-1$ + + /** + * Marks the year part of a date. + * + * @since Android 1.0 + */ + public final static Field YEAR = new Field("year", Calendar.YEAR); //$NON-NLS-1$ + + /** + * Marks the month part of a date. + * + * @since Android 1.0 + */ + public final static Field MONTH = new Field("month", Calendar.MONTH); //$NON-NLS-1$ + + /** + * Marks the hour of the day part of a date (0-11). + * + * @since Android 1.0 + */ + public final static Field HOUR_OF_DAY0 = new Field("hour of day", //$NON-NLS-1$ + Calendar.HOUR_OF_DAY); + + /** + * Marks the hour of the day part of a date (1-12). + * + * @since Android 1.0 + */ + public final static Field HOUR_OF_DAY1 = new Field("hour of day 1", -1); //$NON-NLS-1$ + + /** + * Marks the minute part of a time. + * + * @since Android 1.0 + */ + public final static Field MINUTE = new Field("minute", Calendar.MINUTE); //$NON-NLS-1$ + + /** + * Marks the second part of a time. + * + * @since Android 1.0 + */ + public final static Field SECOND = new Field("second", Calendar.SECOND); //$NON-NLS-1$ + + /** + * Marks the millisecond part of a time. + * + * @since Android 1.0 + */ + public final static Field MILLISECOND = new Field("millisecond", //$NON-NLS-1$ + Calendar.MILLISECOND); + + /** + * Marks the day of the week part of a date. + * + * @since Android 1.0 + */ + public final static Field DAY_OF_WEEK = new Field("day of week", //$NON-NLS-1$ + Calendar.DAY_OF_WEEK); + + /** + * Marks the day of the month part of a date. + * + * @since Android 1.0 + */ + public final static Field DAY_OF_MONTH = new Field("day of month", //$NON-NLS-1$ + Calendar.DAY_OF_MONTH); + + /** + * Marks the day of the year part of a date. + * + * @since Android 1.0 + */ + public final static Field DAY_OF_YEAR = new Field("day of year", //$NON-NLS-1$ + Calendar.DAY_OF_YEAR); + + /** + * Marks the day of the week in the month part of a date. + * + * @since Android 1.0 + */ + public final static Field DAY_OF_WEEK_IN_MONTH = new Field( + "day of week in month", Calendar.DAY_OF_WEEK_IN_MONTH); //$NON-NLS-1$ + + /** + * Marks the week of the year part of a date. + * + * @since Android 1.0 + */ + public final static Field WEEK_OF_YEAR = new Field("week of year", //$NON-NLS-1$ + Calendar.WEEK_OF_YEAR); + + /** + * Marks the week of the month part of a date. + * + * @since Android 1.0 + */ + public final static Field WEEK_OF_MONTH = new Field("week of month", //$NON-NLS-1$ + Calendar.WEEK_OF_MONTH); + + /** + * Marks the time indicator part of a date. + * + * @since Android 1.0 + */ + public final static Field AM_PM = new Field("am pm", Calendar.AM_PM); //$NON-NLS-1$ + + /** + * Marks the hour part of a date (0-11). + * + * @since Android 1.0 + */ + public final static Field HOUR0 = new Field("hour", Calendar.HOUR); //$NON-NLS-1$ + + /** + * Marks the hour part of a date (1-12). + * + * @since Android 1.0 + */ + public final static Field HOUR1 = new Field("hour 1", -1); //$NON-NLS-1$ + + /** + * Marks the time zone part of a date. + * + * @since Android 1.0 + */ + public final static Field TIME_ZONE = new Field("time zone", -1); //$NON-NLS-1$ + + /** + * The calendar field that this field represents. + */ + private int calendarField = -1; + + /** + * Constructs a new instance of {@code DateFormat.Field} with the given + * fieldName and calendar field. + * + * @param fieldName + * the field name. + * @param calendarField + * the calendar field type of the field. + * @since Android 1.0 + */ + protected Field(String fieldName, int calendarField) { + super(fieldName); + this.calendarField = calendarField; + if (calendarField != -1 + && table.get(new Integer(calendarField)) == null) { + table.put(new Integer(calendarField), this); + } + } + + /** + * Returns the Calendar field that this field represents. + * + * @return the calendar field. + * @since Android 1.0 + */ + public int getCalendarField() { + return calendarField; + } + + /** + * Returns the {@code DateFormat.Field} instance for the given calendar + * field. + * + * @param calendarField + * a calendar field constant. + * @return the {@code DateFormat.Field} corresponding to + * {@code calendarField}. + * @throws IllegalArgumentException + * if {@code calendarField} is negative or greater than the + * field count of {@code Calendar}. + * @since Android 1.0 + */ + public static Field ofCalendarField(int calendarField) { + if (calendarField < 0 || calendarField >= Calendar.FIELD_COUNT) { + throw new IllegalArgumentException(); + } + + return table.get(new Integer(calendarField)); + } + + /** + * Resolves instances that are deserialized to the constant + * {@code DateFormat.Field} values. + * + * @return the resolved field object. + * @throws InvalidObjectException + * if an error occurs while resolving the field object. + * @since Android 1.0 + */ + @Override + protected Object readResolve() throws InvalidObjectException { + if (calendarField != -1) { + try { + Field result = ofCalendarField(calendarField); + if (result != null && this.equals(result)) { + return result; + } + } catch (IllegalArgumentException e) { + // text.02=Unknown attribute + throw new InvalidObjectException(Messages + .getString("text.02")); //$NON-NLS-1$ + } + } else { + if (this.equals(TIME_ZONE)) { + return TIME_ZONE; + } + if (this.equals(HOUR1)) { + return HOUR1; + } + if (this.equals(HOUR_OF_DAY1)) { + return HOUR_OF_DAY1; + } + } + // text.02=Unknown attribute + throw new InvalidObjectException(Messages.getString("text.02")); //$NON-NLS-1$ + } + } + + private static void checkDateStyle(int style) { + if (!(style == SHORT || style == MEDIUM || style == LONG + || style == FULL || style == DEFAULT)) { + // text.0E=Illegal date style: {0} + throw new IllegalArgumentException(Messages.getString( + "text.0E", style)); //$NON-NLS-1$ + } + } + + private static void checkTimeStyle(int style) { + if (!(style == SHORT || style == MEDIUM || style == LONG + || style == FULL || style == DEFAULT)) { + // text.0F=Illegal time style: {0} + throw new IllegalArgumentException(Messages.getString( + "text.0F", style)); //$NON-NLS-1$ + } + } +} diff --git a/text/src/main/java/java/text/DateFormatSymbols.java b/text/src/main/java/java/text/DateFormatSymbols.java new file mode 100644 index 0000000..22c74e9 --- /dev/null +++ b/text/src/main/java/java/text/DateFormatSymbols.java @@ -0,0 +1,485 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** +******************************************************************************* +* Copyright (C) 1996-2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc and some of the method descriptions are copied from ICU4J +// source files. Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// END android-note + +package java.text; + +import java.io.Serializable; +// BEGIN android-added +import java.io.IOException; +import java.io.ObjectOutputStream; +// END android-added +import java.util.Arrays; +import java.util.Locale; +import java.util.ResourceBundle; + +// BEGIN android-added +import com.ibm.icu4jni.util.Resources; +// END android-added +/** + * Encapsulates localizable date-time formatting data, such as the names of the + * months, the names of the days of the week, and the time zone data. + * {@code DateFormat} and {@code SimpleDateFormat} both use + * {@code DateFormatSymbols} to encapsulate this information. + * <p> + * Typically you shouldn't use {@code DateFormatSymbols} directly. Rather, you + * are encouraged to create a date/time formatter with the {@code DateFormat} + * class's factory methods: {@code getTimeInstance}, {@code getDateInstance}, + * or {@code getDateTimeInstance}. These methods automatically create a + * {@code DateFormatSymbols} for the formatter so that you don't have to. After + * the formatter is created, you may modify its format pattern using the + * {@code setPattern} method. For more information about creating formatters + * using {@code DateFormat}'s factory methods, see {@link DateFormat}. + * </p> + * <p> + * If you decide to create a date/time formatter with a specific format pattern + * for a specific locale, you can do so with: + * </p> + * <blockquote> + * + * <pre> + * new SimpleDateFormat(aPattern, new DateFormatSymbols(aLocale)). + * </pre> + * + * </blockquote> + * <p> + * {@code DateFormatSymbols} objects can be cloned. When you obtain a + * {@code DateFormatSymbols} object, feel free to modify the date/time + * formatting data. For instance, you can replace the localized date/time format + * pattern characters with the ones that you feel easy to remember or you can + * change the representative cities to your favorite ones. + * </p> + * <p> + * New {@code DateFormatSymbols} subclasses may be added to support + * {@code SimpleDateFormat} for date/time formatting for additional locales. + * </p> + * + * @see DateFormat + * @see SimpleDateFormat + * @since Android 1.0 + */ +public class DateFormatSymbols implements Serializable, Cloneable { + + private static final long serialVersionUID = -5987973545549424702L; + + private String localPatternChars; + + String[] ampms, eras, months, shortMonths, shortWeekdays, weekdays; + + String[][] zoneStrings; + +// BEGIN android-added + /** + * Locale, necessary to lazily load time zone strings. We force the time + * zone names to load upon serialization, so this will never be needed + * post deserialization. + */ + transient final Locale locale; + + /** + * Gets zone strings, initializing them if necessary. Does not create + * a defensive copy, so make sure you do so before exposing the returned + * arrays to clients. + */ + synchronized String[][] internalZoneStrings() { + if (zoneStrings == null) { + zoneStrings = Resources.getDisplayTimeZones(locale.toString()); + } + return zoneStrings; + } +// END android-added + + /** + * Constructs a new {@code DateFormatSymbols} instance containing the + * symbols for the default locale. + * + * @since Android 1.0 + */ + public DateFormatSymbols() { + this(Locale.getDefault()); + } + + /** + * Constructs a new {@code DateFormatSymbols} instance containing the + * symbols for the specified locale. + * + * @param locale + * the locale. + * @since Android 1.0 + */ + public DateFormatSymbols(Locale locale) { + ResourceBundle bundle = Format.getBundle(locale); + localPatternChars = bundle.getString("LocalPatternChars"); //$NON-NLS-1$ + ampms = bundle.getStringArray("ampm"); //$NON-NLS-1$ + eras = bundle.getStringArray("eras"); //$NON-NLS-1$ + months = bundle.getStringArray("months"); //$NON-NLS-1$ + shortMonths = bundle.getStringArray("shortMonths"); //$NON-NLS-1$ + shortWeekdays = bundle.getStringArray("shortWeekdays"); //$NON-NLS-1$ + weekdays = bundle.getStringArray("weekdays"); //$NON-NLS-1$ + // BEGIN android-changed + // zoneStrings = (String[][]) bundle.getObject("timezones"); //$NON-NLS-1$ + this.locale = locale; + // END android-changed + } + + @Override + public Object clone() { + // BEGIN android-changed + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + // END android-changed + } + + /** + * Compares this object with the specified object and indicates if they are + * equal. + * + * @param object + * the object to compare with this object. + * @return {@code true} if {@code object} is an instance of + * {@code DateFormatSymbols} and has the same symbols as this + * object, {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof DateFormatSymbols)) { + return false; + } + DateFormatSymbols obj = (DateFormatSymbols) object; + if (!localPatternChars.equals(obj.localPatternChars)) { + return false; + } + if (!Arrays.equals(ampms, obj.ampms)) { + return false; + } + if (!Arrays.equals(eras, obj.eras)) { + return false; + } + if (!Arrays.equals(months, obj.months)) { + return false; + } + if (!Arrays.equals(shortMonths, obj.shortMonths)) { + return false; + } + if (!Arrays.equals(shortWeekdays, obj.shortWeekdays)) { + return false; + } + if (!Arrays.equals(weekdays, obj.weekdays)) { + return false; + } + // BEGIN android-changed + // Quick check that may keep us from having to load the zone strings. + if (zoneStrings == null && obj.zoneStrings == null + && !locale.equals(obj.locale)) { + return false; + } + // Make sure zone strings are loaded. + internalZoneStrings(); + obj.internalZoneStrings(); + // END android-changed + if (zoneStrings.length != obj.zoneStrings.length) { + return false; + } + for (String[] element : zoneStrings) { + if (element.length != element.length) { + return false; + } + for (int j = 0; j < element.length; j++) { + if (element[j] != element[j] + && !(element[j].equals(element[j]))) { + return false; + } + } + } + return true; + } + + /** + * Returns the array of strings which represent AM and PM. Use the + * {@link java.util.Calendar} constants {@code Calendar.AM} and + * {@code Calendar.PM} as indices for the array. + * + * @return an array of strings. + * @since Android 1.0 + */ + public String[] getAmPmStrings() { + return ampms.clone(); + } + + /** + * Returns the array of strings which represent BC and AD. Use the + * {@link java.util.Calendar} constants {@code GregorianCalendar.BC} and + * {@code GregorianCalendar.AD} as indices for the array. + * + * @return an array of strings. + * @since Android 1.0 + */ + public String[] getEras() { + return eras.clone(); + } + + /** + * Returns the pattern characters used by {@link SimpleDateFormat} to + * specify date and time fields. + * + * @return a string containing the pattern characters. + * @since Android 1.0 + */ + public String getLocalPatternChars() { + return localPatternChars; + } + + /** + * Returns the array of strings containing the full names of the months. Use + * the {@link java.util.Calendar} constants {@code Calendar.JANUARY} etc. as + * indices for the array. + * + * @return an array of strings. + * @since Android 1.0 + */ + public String[] getMonths() { + return months.clone(); + } + + /** + * Returns the array of strings containing the abbreviated names of the + * months. Use the {@link java.util.Calendar} constants + * {@code Calendar.JANUARY} etc. as indices for the array. + * + * @return an array of strings. + * @since Android 1.0 + */ + public String[] getShortMonths() { + return shortMonths.clone(); + } + + /** + * Returns the array of strings containing the abbreviated names of the days + * of the week. Use the {@link java.util.Calendar} constants + * {@code Calendar.SUNDAY} etc. as indices for the array. + * + * @return an array of strings. + * @since Android 1.0 + */ + public String[] getShortWeekdays() { + return shortWeekdays.clone(); + } + + /** + * Returns the array of strings containing the full names of the days of the + * week. Use the {@link java.util.Calendar} constants + * {@code Calendar.SUNDAY} etc. as indices for the array. + * + * @return an array of strings. + * @since Android 1.0 + */ + public String[] getWeekdays() { + return weekdays.clone(); + } + + /** + * Returns the two-dimensional array of strings containing the names of the + * time zones. Each element in the array is an array of five strings, the + * first is a TimeZone ID, the second and third are the full and abbreviated + * time zone names for standard time, and the fourth and fifth are the full + * and abbreviated names for daylight time. + * + * @return a two-dimensional array of strings. + * @since Android 1.0 + */ + public String[][] getZoneStrings() { + // BEGIN android-added + String[][] zoneStrings = internalZoneStrings(); + // END android-added + String[][] clone = new String[zoneStrings.length][]; + for (int i = zoneStrings.length; --i >= 0;) { + clone[i] = zoneStrings[i].clone(); + } + return clone; + } + + @Override + public int hashCode() { + int hashCode; + hashCode = localPatternChars.hashCode(); + for (String element : ampms) { + hashCode += element.hashCode(); + } + for (String element : eras) { + hashCode += element.hashCode(); + } + for (String element : months) { + hashCode += element.hashCode(); + } + for (String element : shortMonths) { + hashCode += element.hashCode(); + } + for (String element : shortWeekdays) { + hashCode += element.hashCode(); + } + for (String element : weekdays) { + hashCode += element.hashCode(); + } + // BEGIN android-added + String[][] zoneStrings = internalZoneStrings(); + // END android-added + for (String[] element : zoneStrings) { + for (int j = 0; j < element.length; j++) { + hashCode += element[j].hashCode(); + } + } + return hashCode; + } + + /** + * Sets the array of strings which represent AM and PM. Use the + * {@link java.util.Calendar} constants {@code Calendar.AM} and + * {@code Calendar.PM} as indices for the array. + * + * @param data + * the array of strings for AM and PM. + * @since Android 1.0 + */ + public void setAmPmStrings(String[] data) { + ampms = data.clone(); + } + + /** + * Sets the array of Strings which represent BC and AD. Use the + * {@link java.util.Calendar} constants {@code GregorianCalendar.BC} and + * {@code GregorianCalendar.AD} as indices for the array. + * + * @param data + * the array of strings for BC and AD. + * @since Android 1.0 + */ + public void setEras(String[] data) { + eras = data.clone(); + } + + /** + * Sets the pattern characters used by {@link SimpleDateFormat} to specify + * date and time fields. + * + * @param data + * the string containing the pattern characters. + * @since Android 1.0 + */ + public void setLocalPatternChars(String data) { + if (data == null) { + throw new NullPointerException(); + } + localPatternChars = data; + } + + /** + * Sets the array of strings containing the full names of the months. Use + * the {@link java.util.Calendar} constants {@code Calendar.JANUARY} etc. as + * indices for the array. + * + * @param data + * the array of strings. + * @since Android 1.0 + */ + public void setMonths(String[] data) { + months = data.clone(); + } + + /** + * Sets the array of strings containing the abbreviated names of the months. + * Use the {@link java.util.Calendar} constants {@code Calendar.JANUARY} + * etc. as indices for the array. + * + * @param data + * the array of strings. + * @since Android 1.0 + */ + public void setShortMonths(String[] data) { + shortMonths = data.clone(); + } + + /** + * Sets the array of strings containing the abbreviated names of the days of + * the week. Use the {@link java.util.Calendar} constants + * {@code Calendar.SUNDAY} etc. as indices for the array. + * + * @param data + * the array of strings. + * @since Android 1.0 + */ + public void setShortWeekdays(String[] data) { + shortWeekdays = data.clone(); + } + + /** + * Sets the array of strings containing the full names of the days of the + * week. Use the {@link java.util.Calendar} constants + * {@code Calendar.SUNDAY} etc. as indices for the array. + * + * @param data + * the array of strings. + * @since Android 1.0 + */ + public void setWeekdays(String[] data) { + weekdays = data.clone(); + } + + /** + * Sets the two-dimensional array of strings containing the names of the + * time zones. Each element in the array is an array of five strings, the + * first is a TimeZone ID, and second and third are the full and abbreviated + * time zone names for standard time, and the fourth and fifth are the full + * and abbreviated names for daylight time. + * + * @param data + * the two-dimensional array of strings. + * @since Android 1.0 + */ + public void setZoneStrings(String[][] data) { + zoneStrings = data.clone(); + } + + // BEGIN android-added + private void writeObject(ObjectOutputStream out) + throws IOException { + // Ensure internal zone strings are initialized to ensure backward + // compatibility. + internalZoneStrings(); + + out.defaultWriteObject(); + } + // END android-added +} diff --git a/text/src/main/java/java/text/DecimalFormat.java b/text/src/main/java/java/text/DecimalFormat.java new file mode 100644 index 0000000..393a2e5 --- /dev/null +++ b/text/src/main/java/java/text/DecimalFormat.java @@ -0,0 +1,1548 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** +******************************************************************************* +* Copyright (C) 1996-2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc and some of the method descriptions are copied from ICU4J +// source files. Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// The icu implementation used was changed from icu4j to icu4jni. +// END android-note + +package java.text; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Currency; +import java.util.Locale; + +import org.apache.harmony.text.internal.nls.Messages; + +/** + * A concrete subclass of {@link NumberFormat} that formats decimal numbers. It + * has a variety of features designed to make it possible to parse and format + * numbers in any locale, including support for Western, Arabic, or Indic + * digits. It also supports different flavors of numbers, including integers + * ("123"), fixed-point numbers ("123.4"), scientific notation ("1.23E4"), + * percentages ("12%"), and currency amounts ("$123"). All of these flavors can + * be easily localized. + * <p> + * <strong>This is an enhanced version of {@code DecimalFormat} that is based on + * the standard version in the RI. New or changed functionality is labeled + * <strong><font color="red">NEW</font></strong>.</strong> + * </p> + * <p> + * To obtain a {@link NumberFormat} for a specific locale (including the default + * locale), call one of {@code NumberFormat}'s factory methods such as + * {@code NumberFormat.getInstance}. Do not call the {@code DecimalFormat} + * constructors directly, unless you know what you are doing, since the + * {@link NumberFormat} factory methods may return subclasses other than + * {@code DecimalFormat}. If you need to customize the format object, do + * something like this: <blockquote> + * + * <pre> + * NumberFormat f = NumberFormat.getInstance(loc); + * if (f instanceof DecimalFormat) { + * ((DecimalFormat)f).setDecimalSeparatorAlwaysShown(true); + * } + * </pre> + * + * </blockquote> + * <h5>Example:</h5> + * <blockquote> + * + * <pre> + * // Print out a number using the localized number, currency, + * // and percent format for each locale + * Locale[] locales = NumberFormat.getAvailableLocales(); + * double myNumber = -1234.56; + * NumberFormat format; + * for (int j = 0; j < 3; ++j) { + * System.out.println("FORMAT"); + * for (int i = 0; i < locales.length; ++i) { + * if (locales[i].getCountry().length() == 0) { + * // Skip language-only locales + * continue; + * } + * System.out.print(locales[i].getDisplayName()); + * switch (j) { + * case 0: + * format = NumberFormat.getInstance(locales[i]); + * break; + * case 1: + * format = NumberFormat.getCurrencyInstance(locales[i]); + * break; + * default: + * format = NumberFormat.getPercentInstance(locales[i]); + * break; + * } + * try { + * // Assume format is a DecimalFormat + * System.out.print(": "; + ((DecimalFormat)format).toPattern() + " -> " + * + form.format(myNumber)); + * } catch (Exception e) { + * } + * try { + * System.out.println(" -> " + format.parse(form.format(myNumber))); + * } catch (ParseException e) { + * } + * } + * } + * </pre> + * + * </blockquote> + * <h4>Patterns</h4> + * <p> + * A {@code DecimalFormat} consists of a <em>pattern</em> and a set of + * <em>symbols</em>. The pattern may be set directly using + * {@link #applyPattern(String)}, or indirectly using other API methods which + * manipulate aspects of the pattern, such as the minimum number of integer + * digits. The symbols are stored in a {@link DecimalFormatSymbols} object. When + * using the {@link NumberFormat} factory methods, the pattern and symbols are + * read from ICU's locale data. + * </p> + * <h4>Special Pattern Characters</h4> + * <p> + * Many characters in a pattern are taken literally; they are matched during + * parsing and are written out unchanged during formatting. On the other hand, + * special characters stand for other characters, strings, or classes of + * characters. For example, the '#' character is replaced by a localized digit. + * Often the replacement character is the same as the pattern character; in the + * U.S. locale, the ',' grouping character is replaced by ','. However, the + * replacement is still happening, and if the symbols are modified, the grouping + * character changes. Some special characters affect the behavior of the + * formatter by their presence; for example, if the percent character is seen, + * then the value is multiplied by 100 before being displayed. + * </p> + * <p> + * To insert a special character in a pattern as a literal, that is, without any + * special meaning, the character must be quoted. There are some exceptions to + * this which are noted below. + * </p> + * <p> + * The characters listed here are used in non-localized patterns. Localized + * patterns use the corresponding characters taken from this formatter's + * {@link DecimalFormatSymbols} object instead, and these characters lose their + * special status. Two exceptions are the currency sign and quote, which are not + * localized. + * </p> + * <blockquote> <table border="0" cellspacing="3" cellpadding="0" summary="Chart + * showing symbol, location, localized, and meaning."> + * <tr bgcolor="#ccccff"> + * <th align="left">Symbol</th> + * <th align="left">Location</th> + * <th align="left">Localized?</th> + * <th align="left">Meaning</th> + * </tr> + * <tr valign="top"> + * <td>{@code 0}</td> + * <td>Number</td> + * <td>Yes</td> + * <td>Digit.</td> + * </tr> + * <tr valign="top"> + * <td>{@code @}</td> + * <td>Number</td> + * <td>No</td> + * <td><strong><font color="red">NEW</font> </strong> Significant + * digit.</td> + * </tr> + * <tr valign="top" bgcolor="#eeeeff"> + * <td>{@code #}</td> + * <td>Number</td> + * <td>Yes</td> + * <td>Digit, leading zeroes are not shown.</td> + * </tr> + * <tr valign="top"> + * <td>{@code .}</td> + * <td>Number</td> + * <td>Yes</td> + * <td>Decimal separator or monetary decimal separator.</td> + * </tr> + * <tr valign="top" bgcolor="#eeeeff"> + * <td>{@code -}</td> + * <td>Number</td> + * <td>Yes</td> + * <td>Minus sign.</td> + * </tr> + * <tr valign="top"> + * <td>{@code ,}</td> + * <td>Number</td> + * <td>Yes</td> + * <td>Grouping separator.</td> + * </tr> + * <tr valign="top" bgcolor="#eeeeff"> + * <td>{@code E}</td> + * <td>Number</td> + * <td>Yes</td> + * <td>Separates mantissa and exponent in scientific notation. + * <em>Does not need to be quoted in prefix or suffix.</em></td> + * </tr> + * <tr valign="top"> + * <td>{@code +}</td> + * <td>Exponent</td> + * <td>Yes</td> + * <td><strong><font color="red">NEW</font> </strong> Prefix + * positive exponents with localized plus sign. + * <em>Does not need to be quoted in prefix or suffix.</em></td> + * </tr> + * <tr valign="top" bgcolor="#eeeeff"> + * <td>{@code ;}</td> + * <td>Subpattern boundary</td> + * <td>Yes</td> + * <td>Separates positive and negative subpatterns.</td> + * </tr> + * <tr valign="top"> + * <td>{@code %}</td> + * <td>Prefix or suffix</td> + * <td>Yes</td> + * <td>Multiply by 100 and show as percentage.</td> + * </tr> + * <tr valign="top" bgcolor="#eeeeff"> + * <td>{@code \u2030} ({@code \u2030})</td> + * <td>Prefix or suffix</td> + * <td>Yes</td> + * <td>Multiply by 1000 and show as per mille.</td> + * </tr> + * <tr valign="top"> + * <td>{@code ¤} ({@code \u00A4})</td> + * <td>Prefix or suffix</td> + * <td>No</td> + * <td>Currency sign, replaced by currency symbol. If doubled, replaced by + * international currency symbol. If present in a pattern, the monetary decimal + * separator is used instead of the decimal separator.</td> + * </tr> + * <tr valign="top" bgcolor="#eeeeff"> + * <td>{@code '}</td> + * <td>Prefix or suffix</td> + * <td>No</td> + * <td>Used to quote special characters in a prefix or suffix, for example, + * {@code "'#'#"} formats 123 to {@code "#123"}. To create a single quote + * itself, use two in a row: {@code "# o''clock"}.</td> + * </tr> + * <tr valign="top"> + * <td>{@code *}</td> + * <td>Prefix or suffix boundary</td> + * <td>Yes</td> + * <td><strong><font color="red">NEW</font> </strong> Pad escape, + * precedes pad character. </td> + * </tr> + * </table> </blockquote> + * <p> + * A {@code DecimalFormat} pattern contains a postive and negative subpattern, + * for example, "#,##0.00;(#,##0.00)". Each subpattern has a prefix, a numeric + * part and a suffix. If there is no explicit negative subpattern, the negative + * subpattern is the localized minus sign prefixed to the positive subpattern. + * That is, "0.00" alone is equivalent to "0.00;-0.00". If there is an explicit + * negative subpattern, it serves only to specify the negative prefix and + * suffix; the number of digits, minimal digits, and other characteristics are + * ignored in the negative subpattern. This means that "#,##0.0#;(#)" produces + * precisely the same result as "#,##0.0#;(#,##0.0#)". + * <p> + * The prefixes, suffixes, and various symbols used for infinity, digits, + * thousands separators, decimal separators, etc. may be set to arbitrary + * values, and they will appear properly during formatting. However, care must + * be taken that the symbols and strings do not conflict, or parsing will be + * unreliable. For example, either the positive and negative prefixes or the + * suffixes must be distinct for {@link #parse} to be able to distinguish + * positive from negative values. Another example is that the decimal separator + * and thousands separator should be distinct characters, or parsing will be + * impossible. + * <p> + * The <em>grouping separator</em> is a character that separates clusters of + * integer digits to make large numbers more legible. It is commonly used for + * thousands, but in some locales it separates ten-thousands. The <em>grouping + * size</em> + * is the number of digits between the grouping separators, such as 3 for + * "100,000,000" or 4 for "1 0000 0000". There are actually two different + * grouping sizes: One used for the least significant integer digits, the + * <em>primary grouping size</em>, and one used for all others, the + * <em>secondary grouping size</em>. In most locales these are the same, but + * sometimes they are different. For example, if the primary grouping interval + * is 3, and the secondary is 2, then this corresponds to the pattern + * "#,##,##0", and the number 123456789 is formatted as "12,34,56,789". If a + * pattern contains multiple grouping separators, the interval between the last + * one and the end of the integer defines the primary grouping size, and the + * interval between the last two defines the secondary grouping size. All others + * are ignored, so "#,##,###,####", "###,###,####" and "##,#,###,####" produce + * the same result. + * <p> + * Illegal patterns, such as "#.#.#" or "#.###,###", will cause + * {@code DecimalFormat} to throw an {@link IllegalArgumentException} with a + * message that describes the problem. + * <h4>Pattern BNF</h4> + * + * <pre> + * pattern := subpattern (';' subpattern)? + * subpattern := prefix? number exponent? suffix? + * number := (integer ('.' fraction)?) | sigDigits + * prefix := '\\u0000'..'\\uFFFD' - specialCharacters + * suffix := '\\u0000'..'\\uFFFD' - specialCharacters + * integer := '#'* '0'* '0' + * fraction := '0'* '#'* + * sigDigits := '#'* '@' '@'* '#'* + * exponent := 'E' '+'? '0'* '0' + * padSpec := '*' padChar + * padChar := '\\u0000'..'\\uFFFD' - quote + * + * Notation: + * X* 0 or more instances of X + * X? 0 or 1 instances of X + * X|Y either X or Y + * C..D any character from C up to D, inclusive + * S-T characters in S, except those in T + * </pre> + * + * The first subpattern is for positive numbers. The second (optional) + * subpattern is for negative numbers. + * <p> + * Not indicated in the BNF syntax above: + * <ul> + * <li>The grouping separator ',' can occur inside the integer and sigDigits + * elements, between any two pattern characters of that element, as long as the + * integer or sigDigits element is not followed by the exponent element. + * <li><font color="red"><strong>NEW</strong> </font> Two + * grouping intervals are recognized: The one between the decimal point and the + * first grouping symbol and the one between the first and second grouping + * symbols. These intervals are identical in most locales, but in some locales + * they differ. For example, the pattern "#,##,###" formats the number + * 123456789 as "12,34,56,789".</li> + * <li> <strong><font color="red">NEW</font> </strong> The pad + * specifier {@code padSpec} may appear before the prefix, after the prefix, + * before the suffix, after the suffix or not at all. + * </ul> + * <h4>Parsing</h4> + * <p> + * {@code DecimalFormat} parses all Unicode characters that represent decimal + * digits, as defined by {@link Character#digit(int, int)}. In addition, + * {@code DecimalFormat} also recognizes as digits the ten consecutive + * characters starting with the localized zero digit defined in the + * {@link DecimalFormatSymbols} object. During formatting, the + * {@link DecimalFormatSymbols}-based digits are written out. + * <p> + * During parsing, grouping separators are ignored. + * <p> + * If {@link #parse(String, ParsePosition)} fails to parse a string, it returns + * {@code null} and leaves the parse position unchanged. + * <h4>Formatting</h4> + * <p> + * Formatting is guided by several parameters, all of which can be specified + * either using a pattern or using the API. The following description applies to + * formats that do not use <a href="#sci">scientific notation</a> or <a + * href="#sigdig">significant digits</a>. + * <ul> + * <li>If the number of actual integer digits exceeds the + * <em>maximum integer digits</em>, then only the least significant digits + * are shown. For example, 1997 is formatted as "97" if maximum integer digits + * is set to 2. + * <li>If the number of actual integer digits is less than the + * <em>minimum integer digits</em>, then leading zeros are added. For + * example, 1997 is formatted as "01997" if minimum integer digits is set to 5. + * <li>If the number of actual fraction digits exceeds the <em>maximum + * fraction digits</em>, + * then half-even rounding is performed to the maximum fraction digits. For + * example, 0.125 is formatted as "0.12" if the maximum fraction digits is 2. + * <li>If the number of actual fraction digits is less than the + * <em>minimum fraction digits</em>, then trailing zeros are added. For + * example, 0.125 is formatted as "0.1250" if the mimimum fraction digits is set + * to 4. + * <li>Trailing fractional zeros are not displayed if they occur <em>j</em> + * positions after the decimal, where <em>j</em> is less than the maximum + * fraction digits. For example, 0.10004 is formatted as "0.1" if the maximum + * fraction digits is four or less. + * </ul> + * <p> + * <strong>Special Values</strong> + * <p> + * {@code NaN} is represented as a single character, typically + * {@code \uFFFD}. This character is determined by the + * {@link DecimalFormatSymbols} object. This is the only value for which the + * prefixes and suffixes are not used. + * <p> + * Infinity is represented as a single character, typically {@code \u221E}, + * with the positive or negative prefixes and suffixes applied. The infinity + * character is determined by the {@link DecimalFormatSymbols} object. <a + * name="sci"> + * <h4>Scientific Notation</h4> + * </a> + * <p> + * Numbers in scientific notation are expressed as the product of a mantissa and + * a power of ten, for example, 1234 can be expressed as 1.234 x 10<sup>3</sup>. + * The mantissa is typically in the half-open interval [1.0, 10.0) or sometimes + * [0.0, 1.0), but it does not need to be. {@code DecimalFormat} supports + * arbitrary mantissas. {@code DecimalFormat} can be instructed to use + * scientific notation through the API or through the pattern. In a pattern, the + * exponent character immediately followed by one or more digit characters + * indicates scientific notation. Example: "0.###E0" formats the number 1234 as + * "1.234E3". + * <ul> + * <li>The number of digit characters after the exponent character gives the + * minimum exponent digit count. There is no maximum. Negative exponents are + * formatted using the localized minus sign, <em>not</em> the prefix and + * suffix from the pattern. This allows patterns such as "0.###E0 m/s". To + * prefix positive exponents with a localized plus sign, specify '+' between the + * exponent and the digits: "0.###E+0" will produce formats "1E+1", "1E+0", + * "1E-1", etc. (In localized patterns, use the localized plus sign rather than + * '+'.) + * <li>The minimum number of integer digits is achieved by adjusting the + * exponent. Example: 0.00123 formatted with "00.###E0" yields "12.3E-4". This + * only happens if there is no maximum number of integer digits. If there is a + * maximum, then the minimum number of integer digits is fixed at one. + * <li>The maximum number of integer digits, if present, specifies the exponent + * grouping. The most common use of this is to generate <em>engineering + * notation</em>, + * in which the exponent is a multiple of three, e.g., "##0.###E0". The number + * 12345 is formatted using "##0.###E0" as "12.345E3". + * <li>When using scientific notation, the formatter controls the digit counts + * using significant digits logic. The maximum number of significant digits + * limits the total number of integer and fraction digits that will be shown in + * the mantissa; it does not affect parsing. For example, 12345 formatted with + * "##0.##E0" is "12.3E3". See the section on significant digits for more + * details. + * <li>The number of significant digits shown is determined as follows: If no + * significant digits are used in the pattern then the minimum number of + * significant digits shown is one, the maximum number of significant digits + * shown is the sum of the <em>minimum integer</em> and + * <em>maximum fraction</em> digits, and it is unaffected by the maximum + * integer digits. If this sum is zero, then all significant digits are shown. + * If significant digits are used in the pattern then the number of integer + * digits is fixed at one and there is no exponent grouping. + * <li>Exponential patterns may not contain grouping separators. + * </ul> + * <a name="sigdig"> + * <h4> <strong><font color="red">NEW</font> </strong> Significant + * Digits</h4> + * <p> + * </a> {@code DecimalFormat} has two ways of controlling how many digits are + * shown: (a) significant digit counts or (b) integer and fraction digit counts. + * Integer and fraction digit counts are described above. When a formatter uses + * significant digits counts, the number of integer and fraction digits is not + * specified directly, and the formatter settings for these counts are ignored. + * Instead, the formatter uses as many integer and fraction digits as required + * to display the specified number of significant digits. + * </p> + * <h5>Examples:</h5> + * <blockquote> <table border=0 cellspacing=3 cellpadding=0> + * <tr bgcolor="#ccccff"> + * <th align="left">Pattern</th> + * <th align="left">Minimum significant digits</th> + * <th align="left">Maximum significant digits</th> + * <th align="left">Number</th> + * <th align="left">Output of format()</th> + * </tr> + * <tr valign="top"> + * <td>{@code @@@} + * <td>3</td> + * <td>3</td> + * <td>12345</td> + * <td>{@code 12300}</td> + * </tr> + * <tr valign="top" bgcolor="#eeeeff"> + * <td>{@code @@@}</td> + * <td>3</td> + * <td>3</td> + * <td>0.12345</td> + * <td>{@code 0.123}</td> + * </tr> + * <tr valign="top"> + * <td>{@code @@##}</td> + * <td>2</td> + * <td>4</td> + * <td>3.14159</td> + * <td>{@code 3.142}</td> + * </tr> + * <tr valign="top" bgcolor="#eeeeff"> + * <td>{@code @@##}</td> + * <td>2</td> + * <td>4</td> + * <td>1.23004</td> + * <td>{@code 1.23}</td> + * </tr> + * </table> </blockquote> + * <ul> + * <li>Significant digit counts may be expressed using patterns that specify a + * minimum and maximum number of significant digits. These are indicated by the + * {@code '@'} and {@code '#'} characters. The minimum number of significant + * digits is the number of {@code '@'} characters. The maximum number of + * significant digits is the number of {@code '@'} characters plus the number of + * {@code '#'} characters following on the right. For example, the pattern + * {@code "@@@"} indicates exactly 3 significant digits. The pattern + * {@code "@##"} indicates from 1 to 3 significant digits. Trailing zero digits + * to the right of the decimal separator are suppressed after the minimum number + * of significant digits have been shown. For example, the pattern {@code "@##"} + * formats the number 0.1203 as {@code "0.12"}. + * <li>If a pattern uses significant digits, it may not contain a decimal + * separator, nor the {@code '0'} pattern character. Patterns such as + * {@code "@00"} or {@code "@.###"} are disallowed. + * <li>Any number of {@code '#'} characters may be prepended to the left of the + * leftmost {@code '@'} character. These have no effect on the minimum and + * maximum significant digit counts, but may be used to position grouping + * separators. For example, {@code "#,#@#"} indicates a minimum of one + * significant digit, a maximum of two significant digits, and a grouping size + * of three. + * <li>In order to enable significant digits formatting, use a pattern + * containing the {@code '@'} pattern character. + * <li>In order to disable significant digits formatting, use a pattern that + * does not contain the {@code '@'} pattern character. + * <li>The number of significant digits has no effect on parsing. + * <li>Significant digits may be used together with exponential notation. Such + * patterns are equivalent to a normal exponential pattern with a minimum and + * maximum integer digit count of one, a minimum fraction digit count of the + * number of '@' characters in the pattern - 1, and a maximum fraction digit + * count of the number of '@' and '#' characters in the pattern - 1. For + * example, the pattern {@code "@@###E0"} is equivalent to {@code "0.0###E0"}. + * <li>If signficant digits are in use then the integer and fraction digit + * counts, as set via the API, are ignored. + * </ul> + * <h4> <strong><font color="red">NEW</font> </strong> Padding</h4> + * <p> + * {@code DecimalFormat} supports padding the result of {@code format} to a + * specific width. Padding may be specified either through the API or through + * the pattern syntax. In a pattern, the pad escape character followed by a + * single pad character causes padding to be parsed and formatted. The pad + * escape character is '*' in unlocalized patterns. For example, + * {@code "$*x#,##0.00"} formats 123 to {@code "$xx123.00"}, and 1234 to + * {@code "$1,234.00"}. + * <ul> + * <li>When padding is in effect, the width of the positive subpattern, + * including prefix and suffix, determines the format width. For example, in the + * pattern {@code "* #0 o''clock"}, the format width is 10.</li> + * <li>The width is counted in 16-bit code units (Java {@code char}s).</li> + * <li>Some parameters which usually do not matter have meaning when padding is + * used, because the pattern width is significant with padding. In the pattern "* + * ##,##,#,##0.##", the format width is 14. The initial characters "##,##," do + * not affect the grouping size or maximum integer digits, but they do affect + * the format width.</li> + * <li>Padding may be inserted at one of four locations: before the prefix, + * after the prefix, before the suffix or after the suffix. If padding is + * specified in any other location, {@link #applyPattern} throws an {@link + * IllegalArgumentException}. If there is no prefix, before the prefix and after + * the prefix are equivalent, likewise for the suffix.</li> + * <li>When specified in a pattern, the 16-bit {@code char} immediately + * following the pad escape is the pad character. This may be any character, + * including a special pattern character. That is, the pad escape + * <em>escapes</em> the following character. If there is no character after + * the pad escape, then the pattern is illegal.</li> + * </ul> + * <h4>Synchronization</h4> + * <p> + * {@code DecimalFormat} objects are not synchronized. Multiple threads should + * not access one formatter concurrently. + * + * @see Format + * @see NumberFormat + * @since Android 1.0 + */ +public class DecimalFormat extends NumberFormat { + + private static final long serialVersionUID = 864413376551465018L; + + private transient boolean parseBigDecimal = false; + + private transient DecimalFormatSymbols symbols; + + private transient com.ibm.icu4jni.text.DecimalFormat dform; + + private transient com.ibm.icu4jni.text.DecimalFormatSymbols icuSymbols; + + private static final int CURRENT_SERIAL_VERTION = 3; + + private transient int serialVersionOnStream = 3; + + /** + * Constructs a new {@code DecimalFormat} for formatting and parsing numbers + * for the default locale. + * + * @since Android 1.0 + */ + public DecimalFormat() { + this(getPattern(Locale.getDefault(), "Number")); //$NON-NLS-1$ + } + + /** + * Constructs a new {@code DecimalFormat} using the specified non-localized + * pattern and the {@code DecimalFormatSymbols} for the default Locale. + * + * @param pattern + * the non-localized pattern. + * @exception IllegalArgumentException + * if the pattern cannot be parsed. + * @since Android 1.0 + */ + public DecimalFormat(String pattern) { + this(pattern, new DecimalFormatSymbols()); + } + + /** + * Constructs a new {@code DecimalFormat} using the specified non-localized + * pattern and {@code DecimalFormatSymbols}. + * + * @param pattern + * the non-localized pattern. + * @param value + * the DecimalFormatSymbols. + * @exception IllegalArgumentException + * if the pattern cannot be parsed. + * @since Android 1.0 + */ + public DecimalFormat(String pattern, DecimalFormatSymbols value) { + symbols = (DecimalFormatSymbols) value.clone(); + Locale locale = (Locale) this.getInternalField("locale", symbols); //$NON-NLS-1$ + icuSymbols = new com.ibm.icu4jni.text.DecimalFormatSymbols(locale); + copySymbols(icuSymbols, symbols); + + dform = new com.ibm.icu4jni.text.DecimalFormat(pattern, icuSymbols); + + super.setMaximumFractionDigits(dform.getMaximumFractionDigits()); + super.setMaximumIntegerDigits(dform.getMaximumIntegerDigits()); + super.setMinimumFractionDigits(dform.getMinimumFractionDigits()); + super.setMinimumIntegerDigits(dform.getMinimumIntegerDigits()); + } + + /** + * Changes the pattern of this decimal format to the specified pattern which + * uses localized pattern characters. + * + * @param pattern + * the localized pattern. + * @exception IllegalArgumentException + * if the pattern cannot be parsed. + * @since Android 1.0 + */ + public void applyLocalizedPattern(String pattern) { + dform.applyLocalizedPattern(pattern); + } + + /** + * Changes the pattern of this decimal format to the specified pattern which + * uses non-localized pattern characters. + * + * @param pattern + * the non-localized pattern. + * @exception IllegalArgumentException + * if the pattern cannot be parsed. + * @since Android 1.0 + */ + public void applyPattern(String pattern) { + + dform.applyPattern(pattern); + } + + /** + * Returns a new instance of {@code DecimalFormat} with the same pattern and + * properties as this decimal format. + * + * @return a shallow copy of this decimal format. + * @see java.lang.Cloneable + * @since Android 1.0 + */ + @Override + public Object clone() { + DecimalFormat clone = (DecimalFormat) super.clone(); + clone.dform = (com.ibm.icu4jni.text.DecimalFormat) dform.clone(); + clone.symbols = (DecimalFormatSymbols) symbols.clone(); + return clone; + } + + /** + * Compares the specified object to this decimal format and indicates if + * they are equal. In order to be equal, {@code object} must be an instance + * of {@code DecimalFormat} with the same pattern and properties. + * + * @param object + * the object to compare with this object. + * @return {@code true} if the specified object is equal to this decimal + * format; {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof DecimalFormat)) { + return false; + } + DecimalFormat format = (DecimalFormat) object; + return (this.dform == null ? format.dform == null : this.dform + .equals(format.dform)); + } + + /** + * Formats the specified object using the rules of this decimal format and + * returns an {@code AttributedCharacterIterator} with the formatted number + * and attributes. + * + * @param object + * the object to format. + * @return an AttributedCharacterIterator with the formatted number and + * attributes. + * @throws IllegalArgumentException + * if {@code object} cannot be formatted by this format. + * @throws NullPointerException + * if {@code object} is {@code null}. + * @since Android 1.0 + */ + @Override + public AttributedCharacterIterator formatToCharacterIterator(Object object) { + if (object == null) { + throw new NullPointerException(); + } + return dform.formatToCharacterIterator(object); + } + + /** + * Formats the specified double value as a string using the pattern of this + * decimal format and appends the string to the specified string buffer. + * <p> + * If the {@code field} member of {@code position} contains a value + * specifying a format field, then its {@code beginIndex} and + * {@code endIndex} members will be updated with the position of the first + * occurrence of this field in the formatted text. + * </p> + * + * @param value + * the double to format. + * @param buffer + * the target string buffer to append the formatted double value + * to. + * @param position + * on input: an optional alignment field; on output: the offsets + * of the alignment field in the formatted text. + * @return the string buffer. + * @since Android 1.0 + */ + @Override + public StringBuffer format(double value, StringBuffer buffer, + FieldPosition position) { + return dform.format(value, buffer, position); + } + + /** + * Formats the specified long value as a string using the pattern of this + * decimal format and appends the string to the specified string buffer. + * <p> + * If the {@code field} member of {@code position} contains a value + * specifying a format field, then its {@code beginIndex} and + * {@code endIndex} members will be updated with the position of the first + * occurrence of this field in the formatted text. + * </p> + * + * @param value + * the long to format. + * @param buffer + * the target string buffer to append the formatted long value + * to. + * @param position + * on input: an optional alignment field; on output: the offsets + * of the alignment field in the formatted text. + * @return the string buffer. + * @since Android 1.0 + */ + @Override + public StringBuffer format(long value, StringBuffer buffer, + FieldPosition position) { + return dform.format(value, buffer, position); + } + + /** + * Formats the specified object as a string using the pattern of this + * decimal format and appends the string to the specified string buffer. + * <p> + * If the {@code field} member of {@code position} contains a value + * specifying a format field, then its {@code beginIndex} and + * {@code endIndex} members will be updated with the position of the first + * occurrence of this field in the formatted text. + * </p> + * + * @param number + * the object to format. + * @param toAppendTo + * the target string buffer to append the formatted number to. + * @param pos + * on input: an optional alignment field; on output: the offsets + * of the alignment field in the formatted text. + * @return the string buffer. + * @throws IllegalArgumentException + * if {@code number} is not an instance of {@code Number}. + * @throws NullPointerException + * if {@code toAppendTo} or {@code pos} is {@code null}. + * @since Android 1.0 + */ + @Override + public final StringBuffer format(Object number, StringBuffer toAppendTo, + FieldPosition pos) { + if (!(number instanceof Number)) { + throw new IllegalArgumentException(); + } + if (toAppendTo == null || pos == null) { + throw new NullPointerException(); + } + if (number instanceof BigInteger || number instanceof BigDecimal) { + return dform.format(number, toAppendTo, pos); + } + return super.format(number, toAppendTo, pos); + } + + /** + * Returns the {@code DecimalFormatSymbols} used by this decimal format. + * + * @return a copy of the {@code DecimalFormatSymbols} used by this decimal + * format. + * @since Android 1.0 + */ + public DecimalFormatSymbols getDecimalFormatSymbols() { + return (DecimalFormatSymbols) symbols.clone(); + } + + /** + * Returns the currency used by this decimal format. + * + * @return the currency used by this decimal format. + * @see DecimalFormatSymbols#getCurrency() + * @since Android 1.0 + */ + @Override + public Currency getCurrency() { + final Currency cur = dform.getCurrency(); + final String code = (cur == null) ? "XXX" : cur.getCurrencyCode(); //$NON-NLS-1$ + + return Currency.getInstance(code); + } + + /** + * Returns the number of digits grouped together by the grouping separator. + * This only allows to get the primary grouping size. There is no API to get + * the secondary grouping size. + * + * @return the number of digits grouped together. + * @since Android 1.0 + */ + public int getGroupingSize() { + return dform.getGroupingSize(); + } + + /** + * Returns the multiplier which is applied to the number before formatting + * or after parsing. + * + * @return the multiplier. + * @since Android 1.0 + */ + public int getMultiplier() { + return dform.getMultiplier(); + } + + /** + * Returns the prefix which is formatted or parsed before a negative number. + * + * @return the negative prefix. + * @since Android 1.0 + */ + public String getNegativePrefix() { + return dform.getNegativePrefix(); + } + + /** + * Returns the suffix which is formatted or parsed after a negative number. + * + * @return the negative suffix. + * @since Android 1.0 + */ + public String getNegativeSuffix() { + return dform.getNegativeSuffix(); + } + + /** + * Returns the prefix which is formatted or parsed before a positive number. + * + * @return the positive prefix. + * @since Android 1.0 + */ + public String getPositivePrefix() { + return dform.getPositivePrefix(); + } + + /** + * Returns the suffix which is formatted or parsed after a positive number. + * + * @return the positive suffix. + * @since Android 1.0 + */ + public String getPositiveSuffix() { + return dform.getPositiveSuffix(); + } + + @Override + public int hashCode() { + return dform.hashCode(); + } + + /** + * Indicates whether the decimal separator is shown when there are no + * fractional digits. + * + * @return {@code true} if the decimal separator should always be formatted; + * {@code false} otherwise. + * @since Android 1.0 + */ + public boolean isDecimalSeparatorAlwaysShown() { + return dform.isDecimalSeparatorAlwaysShown(); + } + + /** + * This value indicates whether the return object of the parse operation is + * of type {@code BigDecimal}. This value defaults to {@code false}. + * + * @return {@code true} if parse always returns {@code BigDecimals}, + * {@code false} if the type of the result is {@code Long} or + * {@code Double}. + * @since Android 1.0 + */ + public boolean isParseBigDecimal() { + return this.parseBigDecimal; + } + + /** + * Sets the flag that indicates whether numbers will be parsed as integers. + * When this decimal format is used for parsing and this value is set to + * {@code true}, then the resulting numbers will be of type + * {@code java.lang.Integer}. Special cases are NaN, positive and negative + * infinity, which are still returned as {@code java.lang.Double}. + * + * @param value + * {@code true} that the resulting numbers of parse operations + * will be of type {@code java.lang.Integer} except for the + * special cases described above. + * @since Android 1.0 + */ + @Override + public void setParseIntegerOnly(boolean value) { + dform.setParseIntegerOnly(value); + } + + /** + * Indicates whether parsing with this decimal format will only + * return numbers of type {@code java.lang.Integer}. + * + * @return {@code true} if this {@code DecimalFormat}'s parse method only + * returns {@code java.lang.Integer}; {@code false} otherwise. + * @since Android 1.0 + */ + @Override + public boolean isParseIntegerOnly() { + return dform.isParseIntegerOnly(); + } + + private static final Double NEGATIVE_ZERO_DOUBLE = new Double(-0.0); + + /** + * Parses a {@code Long} or {@code Double} from the specified string + * starting at the index specified by {@code position}. If the string is + * successfully parsed then the index of the {@code ParsePosition} is + * updated to the index following the parsed text. On error, the index is + * unchanged and the error index of {@code ParsePosition} is set to the + * index where the error occurred. + * + * @param string + * the string to parse. + * @param position + * input/output parameter, specifies the start index in + * {@code string} from where to start parsing. If parsing is + * successful, it is updated with the index following the parsed + * text; on error, the index is unchanged and the error index is + * set to the index where the error occurred. + * @return a {@code Long} or {@code Double} resulting from the parse or + * {@code null} if there is an error. The result will be a + * {@code Long} if the parsed number is an integer in the range of a + * long, otherwise the result is a {@code Double}. If + * {@code isParseBigDecimal} is {@code true} then it returns the + * result as a {@code BigDecimal}. + * @since Android 1.0 + */ + @Override + public Number parse(String string, ParsePosition position) { + Number number = dform.parse(string, position); + if (null == number) { + return null; + } + // BEGIN android-removed + // if (this.isParseBigDecimal()) { + // if (number instanceof Long) { + // return new BigDecimal(number.longValue()); + // } + // if ((number instanceof Double) && !((Double) number).isInfinite() + // && !((Double) number).isNaN()) { + // + // return new BigDecimal(number.doubleValue()); + // } + // if (number instanceof BigInteger) { + // return new BigDecimal(number.doubleValue()); + // } + // if (number instanceof com.ibm.icu.math.BigDecimal) { + // return new BigDecimal(number.toString()); + // } + // return number; + // } + // if ((number instanceof com.ibm.icu.math.BigDecimal) + // || (number instanceof BigInteger)) { + // return new Double(number.doubleValue()); + // } + // END android-removed + // BEGIN android-added + if (this.isParseBigDecimal()) { + if (number instanceof Long) { + return new BigDecimal(number.longValue()); + } + if ((number instanceof Double) && !((Double) number).isInfinite() + && !((Double) number).isNaN()) { + + return new BigDecimal(number.toString()); + } + if (number instanceof BigInteger) { + return new BigDecimal(number.toString()); + } + return number; + } + if ((number instanceof BigDecimal) || (number instanceof BigInteger)) { + return new Double(number.doubleValue()); + } + // END android-added + + if (this.isParseIntegerOnly() && number.equals(NEGATIVE_ZERO_DOUBLE)) { + return new Long(0); + } + return number; + + } + + /** + * Sets the {@code DecimalFormatSymbols} used by this decimal format. + * + * @param value + * the {@code DecimalFormatSymbols} to set. + * @since Android 1.0 + */ + public void setDecimalFormatSymbols(DecimalFormatSymbols value) { + if (value != null) { + symbols = (DecimalFormatSymbols) value.clone(); + icuSymbols = dform.getDecimalFormatSymbols(); + copySymbols(icuSymbols, symbols); + dform.setDecimalFormatSymbols(icuSymbols); + } + } + + /** + * Sets the currency used by this decimal format. The min and max fraction + * digits remain the same. + * + * @param currency + * the currency this {@code DecimalFormat} should use. + * @see DecimalFormatSymbols#setCurrency(Currency) + * @since Android 1.0 + */ + @Override + public void setCurrency(Currency currency) { + // BEGIN android-changed + dform.setCurrency(Currency.getInstance(currency + .getCurrencyCode())); + // END android-changed + symbols.setCurrency(currency); + } + + /** + * Sets whether the decimal separator is shown when there are no fractional + * digits. + * + * @param value + * {@code true} if the decimal separator should always be + * formatted; {@code false} otherwise. + * @since Android 1.0 + */ + public void setDecimalSeparatorAlwaysShown(boolean value) { + dform.setDecimalSeparatorAlwaysShown(value); + } + + /** + * Sets the number of digits grouped together by the grouping separator. + * This only allows to set the primary grouping size; the secondary grouping + * size can only be set with a pattern. + * + * @param value + * the number of digits grouped together. + * @since Android 1.0 + */ + public void setGroupingSize(int value) { + dform.setGroupingSize(value); + } + + /** + * Sets whether or not grouping will be used in this format. Grouping + * affects both parsing and formatting. + * + * @param value + * {@code true} if grouping is used; {@code false} otherwise. + * @since Android 1.0 + */ + @Override + public void setGroupingUsed(boolean value) { + dform.setGroupingUsed(value); + } + + /** + * Indicates whether grouping will be used in this format. + * + * @return {@code true} if grouping is used; {@code false} otherwise. + * @since Android 1.0 + */ + @Override + public boolean isGroupingUsed() { + return dform.isGroupingUsed(); + } + + /** + * Sets the maximum number of fraction digits that are printed when + * formatting numbers other than {@code BigDecimal} and {@code BigInteger}. + * If the maximum is less than the number of fraction digits, the least + * significant digits are truncated. If the value passed is bigger than 340 + * then it is replaced by 340. If the value passed is negative then it is + * replaced by 0. + * + * @param value + * the maximum number of fraction digits. + * @since Android 1.0 + */ + @Override + public void setMaximumFractionDigits(int value) { + super.setMaximumFractionDigits(value); + dform.setMaximumFractionDigits(value); + } + + /** + * Sets the maximum number of integer digits that are printed when + * formatting numbers other than {@code BigDecimal} and {@code BigInteger}. + * If the maximum is less than the number of integer digits, the most + * significant digits are truncated. If the value passed is bigger than 309 + * then it is replaced by 309. If the value passed is negative then it is + * replaced by 0. + * + * @param value + * the maximum number of integer digits. + * @since Android 1.0 + */ + @Override + public void setMaximumIntegerDigits(int value) { + super.setMaximumIntegerDigits(value); + dform.setMaximumIntegerDigits(value); + } + + /** + * Sets the minimum number of fraction digits that are printed when + * formatting numbers other than {@code BigDecimal} and {@code BigInteger}. + * If the value passed is bigger than 340 then it is replaced by 340. If the + * value passed is negative then it is replaced by 0. + * + * @param value + * the minimum number of fraction digits. + * @since Android 1.0 + */ + @Override + public void setMinimumFractionDigits(int value) { + super.setMinimumFractionDigits(value); + dform.setMinimumFractionDigits(value); + } + + /** + * Sets the minimum number of integer digits that are printed when + * formatting numbers other than {@code BigDecimal} and {@code BigInteger}. + * If the value passed is bigger than 309 then it is replaced by 309. If the + * value passed is negative then it is replaced by 0. + * + * @param value + * the minimum number of integer digits. + * @since Android 1.0 + */ + @Override + public void setMinimumIntegerDigits(int value) { + super.setMinimumIntegerDigits(value); + dform.setMinimumIntegerDigits(value); + } + + /** + * Sets the multiplier which is applied to the number before formatting or + * after parsing. + * + * @param value + * the multiplier. + * @since Android 1.0 + */ + public void setMultiplier(int value) { + dform.setMultiplier(value); + } + + /** + * Sets the prefix which is formatted or parsed before a negative number. + * + * @param value + * the negative prefix. + * @since Android 1.0 + */ + public void setNegativePrefix(String value) { + dform.setNegativePrefix(value); + } + + /** + * Sets the suffix which is formatted or parsed after a negative number. + * + * @param value + * the negative suffix. + * @since Android 1.0 + */ + public void setNegativeSuffix(String value) { + dform.setNegativeSuffix(value); + } + + /** + * Sets the prefix which is formatted or parsed before a positive number. + * + * @param value + * the positive prefix. + * @since Android 1.0 + */ + public void setPositivePrefix(String value) { + dform.setPositivePrefix(value); + } + + /** + * Sets the suffix which is formatted or parsed after a positive number. + * + * @param value + * the positive suffix. + * @since Android 1.0 + */ + public void setPositiveSuffix(String value) { + dform.setPositiveSuffix(value); + } + + /** + * Sets the behaviour of the parse method. If set to {@code true} then all + * the returned objects will be of type {@code BigDecimal}. + * + * @param newValue + * {@code true} if all the returned objects should be of type + * {@code BigDecimal}; {@code false} otherwise. + * @since Android 1.0 + */ + public void setParseBigDecimal(boolean newValue) { + this.parseBigDecimal = newValue; + } + + /** + * Returns the pattern of this decimal format using localized pattern + * characters. + * + * @return the localized pattern. + * @since Android 1.0 + */ + public String toLocalizedPattern() { + return dform.toLocalizedPattern(); + } + + /** + * Returns the pattern of this decimal format using non-localized pattern + * characters. + * + * @return the non-localized pattern. + * @since Android 1.0 + */ + public String toPattern() { + return dform.toPattern(); + } + + // the fields list to be serialized + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("positivePrefix", String.class), //$NON-NLS-1$ + new ObjectStreamField("positiveSuffix", String.class), //$NON-NLS-1$ + new ObjectStreamField("negativePrefix", String.class), //$NON-NLS-1$ + new ObjectStreamField("negativeSuffix", String.class), //$NON-NLS-1$ + new ObjectStreamField("posPrefixPattern", String.class), //$NON-NLS-1$ + new ObjectStreamField("posSuffixPattern", String.class), //$NON-NLS-1$ + new ObjectStreamField("negPrefixPattern", String.class), //$NON-NLS-1$ + new ObjectStreamField("negSuffixPattern", String.class), //$NON-NLS-1$ + new ObjectStreamField("multiplier", int.class), //$NON-NLS-1$ + new ObjectStreamField("groupingSize", byte.class), //$NON-NLS-1$ + // BEGIN android-added + new ObjectStreamField("groupingUsed", boolean.class), //$NON-NLS-1$ + // END android-added + new ObjectStreamField("decimalSeparatorAlwaysShown", boolean.class), //$NON-NLS-1$ + new ObjectStreamField("parseBigDecimal", boolean.class), //$NON-NLS-1$ + new ObjectStreamField("symbols", DecimalFormatSymbols.class), //$NON-NLS-1$ + new ObjectStreamField("useExponentialNotation", boolean.class), //$NON-NLS-1$ + new ObjectStreamField("minExponentDigits", byte.class), //$NON-NLS-1$ + new ObjectStreamField("maximumIntegerDigits", int.class), //$NON-NLS-1$ + new ObjectStreamField("minimumIntegerDigits", int.class), //$NON-NLS-1$ + new ObjectStreamField("maximumFractionDigits", int.class), //$NON-NLS-1$ + new ObjectStreamField("minimumFractionDigits", int.class), //$NON-NLS-1$ + new ObjectStreamField("serialVersionOnStream", int.class), }; //$NON-NLS-1$ + + /** + * Writes serialized fields following serialized forms specified by Java + * specification. + * + * @param stream + * the output stream to write serialized bytes + * @throws IOException + * if some I/O error occurs + * @throws ClassNotFoundException + */ + private void writeObject(ObjectOutputStream stream) throws IOException, + ClassNotFoundException { + ObjectOutputStream.PutField fields = stream.putFields(); + fields.put("positivePrefix", dform.getPositivePrefix()); //$NON-NLS-1$ + fields.put("positiveSuffix", dform.getPositiveSuffix()); //$NON-NLS-1$ + fields.put("negativePrefix", dform.getNegativePrefix()); //$NON-NLS-1$ + fields.put("negativeSuffix", dform.getNegativeSuffix()); //$NON-NLS-1$ + String posPrefixPattern = (String) this.getInternalField( + "posPrefixPattern", dform); //$NON-NLS-1$ + fields.put("posPrefixPattern", posPrefixPattern); //$NON-NLS-1$ + String posSuffixPattern = (String) this.getInternalField( + "posSuffixPattern", dform); //$NON-NLS-1$ + fields.put("posSuffixPattern", posSuffixPattern); //$NON-NLS-1$ + String negPrefixPattern = (String) this.getInternalField( + "negPrefixPattern", dform); //$NON-NLS-1$ + fields.put("negPrefixPattern", negPrefixPattern); //$NON-NLS-1$ + String negSuffixPattern = (String) this.getInternalField( + "negSuffixPattern", dform); //$NON-NLS-1$ + fields.put("negSuffixPattern", negSuffixPattern); //$NON-NLS-1$ + fields.put("multiplier", dform.getMultiplier()); //$NON-NLS-1$ + fields.put("groupingSize", (byte) dform.getGroupingSize()); //$NON-NLS-1$ + // BEGIN android-added + fields.put("groupingUsed", dform.isGroupingUsed()); //$NON-NLS-1$ + // END android-added + fields.put("decimalSeparatorAlwaysShown", dform //$NON-NLS-1$ + .isDecimalSeparatorAlwaysShown()); + fields.put("parseBigDecimal", parseBigDecimal); //$NON-NLS-1$ + fields.put("symbols", symbols); //$NON-NLS-1$ + boolean useExponentialNotation = ((Boolean) this.getInternalField( + "useExponentialNotation", dform)).booleanValue(); //$NON-NLS-1$ + fields.put("useExponentialNotation", useExponentialNotation); //$NON-NLS-1$ + byte minExponentDigits = ((Byte) this.getInternalField( + "minExponentDigits", dform)).byteValue(); //$NON-NLS-1$ + fields.put("minExponentDigits", minExponentDigits); //$NON-NLS-1$ + fields.put("maximumIntegerDigits", dform.getMaximumIntegerDigits()); //$NON-NLS-1$ + fields.put("minimumIntegerDigits", dform.getMinimumIntegerDigits()); //$NON-NLS-1$ + fields.put("maximumFractionDigits", dform.getMaximumFractionDigits()); //$NON-NLS-1$ + fields.put("minimumFractionDigits", dform.getMinimumFractionDigits()); //$NON-NLS-1$ + fields.put("serialVersionOnStream", CURRENT_SERIAL_VERTION); //$NON-NLS-1$ + stream.writeFields(); + + } + + /** + * Reads serialized fields following serialized forms specified by Java + * specification. + * + * @param stream + * the input stream to read serialized bytes + * @throws IOException + * if some I/O error occurs + * @throws ClassNotFoundException + * if some class of serialized objects or fields cannot be found + */ + private void readObject(ObjectInputStream stream) throws IOException, + ClassNotFoundException { + + ObjectInputStream.GetField fields = stream.readFields(); + String positivePrefix = (String) fields.get("positivePrefix", ""); //$NON-NLS-1$ //$NON-NLS-2$ + String positiveSuffix = (String) fields.get("positiveSuffix", ""); //$NON-NLS-1$ //$NON-NLS-2$ + String negativePrefix = (String) fields.get("negativePrefix", "-"); //$NON-NLS-1$ //$NON-NLS-2$ + String negativeSuffix = (String) fields.get("negativeSuffix", ""); //$NON-NLS-1$ //$NON-NLS-2$ + + String posPrefixPattern = (String) fields.get("posPrefixPattern", ""); //$NON-NLS-1$ //$NON-NLS-2$ + String posSuffixPattern = (String) fields.get("posSuffixPattern", ""); //$NON-NLS-1$ //$NON-NLS-2$ + String negPrefixPattern = (String) fields.get("negPrefixPattern", "-"); //$NON-NLS-1$ //$NON-NLS-2$ + String negSuffixPattern = (String) fields.get("negSuffixPattern", ""); //$NON-NLS-1$ //$NON-NLS-2$ + + int multiplier = fields.get("multiplier", 1); //$NON-NLS-1$ + byte groupingSize = fields.get("groupingSize", (byte) 3); //$NON-NLS-1$ + // BEGIN android-added + boolean groupingUsed = fields.get("groupingUsed", true); //$NON-NLS-1$ + // END android-added + boolean decimalSeparatorAlwaysShown = fields.get( + "decimalSeparatorAlwaysShown", false); //$NON-NLS-1$ + boolean parseBigDecimal = fields.get("parseBigDecimal", false); //$NON-NLS-1$ + symbols = (DecimalFormatSymbols) fields.get("symbols", null); //$NON-NLS-1$ + + boolean useExponentialNotation = fields.get("useExponentialNotation", //$NON-NLS-1$ + false); + byte minExponentDigits = fields.get("minExponentDigits", (byte) 0); //$NON-NLS-1$ + + int maximumIntegerDigits = fields.get("maximumIntegerDigits", 309); //$NON-NLS-1$ + int minimumIntegerDigits = fields.get("minimumIntegerDigits", 309); //$NON-NLS-1$ + int maximumFractionDigits = fields.get("maximumFractionDigits", 340); //$NON-NLS-1$ + int minimumFractionDigits = fields.get("minimumFractionDigits", 340); //$NON-NLS-1$ + this.serialVersionOnStream = fields.get("serialVersionOnStream", 0); //$NON-NLS-1$ + + Locale locale = (Locale) getInternalField("locale", symbols); //$NON-NLS-1$ + // BEGIN android-removed + // dform = new com.ibm.icu4jni.text.DecimalFormat("", //$NON-NLS-1$ + // new com.ibm.icu4jni.text.DecimalFormatSymbols(locale)); + // END android-removed + // BEGIN android-added + icuSymbols = new com.ibm.icu4jni.text.DecimalFormatSymbols(locale); + copySymbols(icuSymbols, symbols); + dform = new com.ibm.icu4jni.text.DecimalFormat("", //$NON-NLS-1$ + icuSymbols); + // END android-added + setInternalField("useExponentialNotation", dform, new Boolean( //$NON-NLS-1$ + useExponentialNotation)); + setInternalField("minExponentDigits", dform, //$NON-NLS-1$ + new Byte(minExponentDigits)); + dform.setPositivePrefix(positivePrefix); + dform.setPositiveSuffix(positiveSuffix); + dform.setNegativePrefix(negativePrefix); + dform.setNegativeSuffix(negativeSuffix); + setInternalField("posPrefixPattern", dform, posPrefixPattern); //$NON-NLS-1$ + setInternalField("posSuffixPattern", dform, posSuffixPattern); //$NON-NLS-1$ + setInternalField("negPrefixPattern", dform, negPrefixPattern); //$NON-NLS-1$ + setInternalField("negSuffixPattern", dform, negSuffixPattern); //$NON-NLS-1$ + dform.setMultiplier(multiplier); + dform.setGroupingSize(groupingSize); + // BEGIN android-added + dform.setGroupingUsed(groupingUsed); + // END android-added + dform.setDecimalSeparatorAlwaysShown(decimalSeparatorAlwaysShown); + dform.setMinimumIntegerDigits(minimumIntegerDigits); + dform.setMaximumIntegerDigits(maximumIntegerDigits); + dform.setMinimumFractionDigits(minimumFractionDigits); + dform.setMaximumFractionDigits(maximumFractionDigits); + this.setParseBigDecimal(parseBigDecimal); + + if (super.getMaximumIntegerDigits() > Integer.MAX_VALUE + || super.getMinimumIntegerDigits() > Integer.MAX_VALUE + || super.getMaximumFractionDigits() > Integer.MAX_VALUE + || super.getMinimumIntegerDigits() > Integer.MAX_VALUE) { + // text.09=The deserialized date is invalid + throw new InvalidObjectException(Messages.getString("text.09")); //$NON-NLS-1$ + } + if (serialVersionOnStream < 3) { + setMaximumIntegerDigits(super.getMinimumIntegerDigits()); + setMinimumIntegerDigits(super.getMinimumIntegerDigits()); + setMaximumFractionDigits(super.getMaximumFractionDigits()); + setMinimumFractionDigits(super.getMinimumFractionDigits()); + } + if (serialVersionOnStream < 1) { + this.setInternalField("useExponentialNotation", dform, //$NON-NLS-1$ + Boolean.FALSE); + } + serialVersionOnStream = 3; + } + + /* + * Copies decimal format symbols from text object to ICU one. + * + * @param icu the object which receives the new values. @param dfs the + * object which contains the new values. + */ + private void copySymbols(final com.ibm.icu4jni.text.DecimalFormatSymbols icu, + final DecimalFormatSymbols dfs) { + // BEGIN android-changed + icu.setCurrency(Currency.getInstance(dfs.getCurrency() + .getCurrencyCode())); + // END android-changed + icu.setCurrencySymbol(dfs.getCurrencySymbol()); + icu.setDecimalSeparator(dfs.getDecimalSeparator()); + icu.setDigit(dfs.getDigit()); + icu.setGroupingSeparator(dfs.getGroupingSeparator()); + icu.setInfinity(dfs.getInfinity()); + icu + .setInternationalCurrencySymbol(dfs + .getInternationalCurrencySymbol()); + icu.setMinusSign(dfs.getMinusSign()); + icu.setMonetaryDecimalSeparator(dfs.getMonetaryDecimalSeparator()); + icu.setNaN(dfs.getNaN()); + icu.setPatternSeparator(dfs.getPatternSeparator()); + icu.setPercent(dfs.getPercent()); + icu.setPerMill(dfs.getPerMill()); + icu.setZeroDigit(dfs.getZeroDigit()); + } + + /* + * Sets private field value by reflection. + * + * @param fieldName the field name to be set @param target the object which + * field to be set @param value the value to be set + */ + private void setInternalField(final String fieldName, final Object target, + final Object value) { + AccessController + .doPrivileged(new PrivilegedAction<java.lang.reflect.Field>() { + public java.lang.reflect.Field run() { + java.lang.reflect.Field field = null; + try { + field = target.getClass().getDeclaredField( + fieldName); + field.setAccessible(true); + field.set(target, value); + } catch (Exception e) { + return null; + } + return field; + } + }); + } + + /* + * Gets private field value by reflection. + * + * @param fieldName the field name to be set @param target the object which + * field to be gotten + */ + private Object getInternalField(final String fieldName, final Object target) { + Object value = AccessController + .doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + Object result = null; + java.lang.reflect.Field field = null; + try { + field = target.getClass().getDeclaredField( + fieldName); + field.setAccessible(true); + result = field.get(target); + } catch (Exception e1) { + return null; + } + return result; + } + }); + return value; + } + +} diff --git a/text/src/main/java/java/text/DecimalFormatSymbols.java b/text/src/main/java/java/text/DecimalFormatSymbols.java new file mode 100644 index 0000000..3415ee8 --- /dev/null +++ b/text/src/main/java/java/text/DecimalFormatSymbols.java @@ -0,0 +1,591 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** +******************************************************************************* +* Copyright (C) 1996-2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc and some of the method descriptions are copied from ICU4J +// source files. Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// END android-note + +package java.text; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Currency; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * Encapsulates the set of symbols (such as the decimal separator, the grouping + * separator, and so on) needed by {@code DecimalFormat} to format numbers. + * {@code DecimalFormat} internally creates an instance of + * {@code DecimalFormatSymbols} from its locale data. If you need to change any + * of these symbols, you can get the {@code DecimalFormatSymbols} object from + * your {@code DecimalFormat} and modify it. + * + * @see java.util.Locale + * @see DecimalFormat + * @since Android 1.0 + */ +public final class DecimalFormatSymbols implements Cloneable, Serializable { + + private static final long serialVersionUID = 5772796243397350300L; + + private final int ZeroDigit = 0, Digit = 1, DecimalSeparator = 2, + GroupingSeparator = 3, PatternSeparator = 4, Percent = 5, + PerMill = 6, Exponent = 7, MonetaryDecimalSeparator = 8, + MinusSign = 9; + + transient char[] patternChars; + + private transient Currency currency; + + private transient Locale locale; + + private String infinity, NaN, currencySymbol, intlCurrencySymbol; + + /** + * Constructs a new {@code DecimalFormatSymbols} containing the symbols for + * the default locale. Best practice is to create a {@code DecimalFormat} + * and then to get the {@code DecimalFormatSymbols} from that object by + * calling {@link DecimalFormat#getDecimalFormatSymbols()}. + * + * @since Android 1.0 + */ + public DecimalFormatSymbols() { + this(Locale.getDefault()); + } + + /** + * Constructs a new DecimalFormatSymbols containing the symbols for the + * specified Locale. Best practice is to create a {@code DecimalFormat} + * and then to get the {@code DecimalFormatSymbols} from that object by + * calling {@link DecimalFormat#getDecimalFormatSymbols()}. + * + * @param locale + * the locale. + * @since Android 1.0 + */ + public DecimalFormatSymbols(Locale locale) { + ResourceBundle bundle = Format.getBundle(locale); + patternChars = bundle.getString("DecimalPatternChars").toCharArray(); //$NON-NLS-1$ + infinity = bundle.getString("Infinity"); //$NON-NLS-1$ + NaN = bundle.getString("NaN"); //$NON-NLS-1$ + this.locale = locale; + try { + currency = Currency.getInstance(locale); + currencySymbol = currency.getSymbol(locale); + intlCurrencySymbol = currency.getCurrencyCode(); + } catch (IllegalArgumentException e) { + currency = Currency.getInstance("XXX"); //$NON-NLS-1$ + currencySymbol = bundle.getString("CurrencySymbol"); //$NON-NLS-1$ + intlCurrencySymbol = bundle.getString("IntCurrencySymbol"); //$NON-NLS-1$ + } + } + + /** + * Returns a new {@code DecimalFormatSymbols} with the same symbols as this + * {@code DecimalFormatSymbols}. + * + * @return a shallow copy of this {@code DecimalFormatSymbols}. + * + * @see java.lang.Cloneable + * @since Android 1.0 + */ + @Override + public Object clone() { + try { + DecimalFormatSymbols symbols = (DecimalFormatSymbols) super.clone(); + symbols.patternChars = patternChars.clone(); + return symbols; + } catch (CloneNotSupportedException e) { + return null; + } + } + + /** + * Compares the specified object to this {@code DecimalFormatSymbols} and + * indicates if they are equal. In order to be equal, {@code object} must be + * an instance of {@code DecimalFormatSymbols} and contain the same symbols. + * + * @param object + * the object to compare with this object. + * @return {@code true} if the specified object is equal to this + * {@code DecimalFormatSymbols}; {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof DecimalFormatSymbols)) { + return false; + } + DecimalFormatSymbols obj = (DecimalFormatSymbols) object; + return Arrays.equals(patternChars, obj.patternChars) + && infinity.equals(obj.infinity) && NaN.equals(obj.NaN) + && currencySymbol.equals(obj.currencySymbol) + && intlCurrencySymbol.equals(obj.intlCurrencySymbol); + } + + /** + * Returns the currency. + * <p> + * {@code null} is returned if {@code setInternationalCurrencySymbol()} has + * been previously called with a value that is not a valid ISO 4217 currency + * code. + * <p> + * + * @return the currency that was set in the constructor or by calling + * {@code setCurrency()} or {@code setInternationalCurrencySymbol()}, + * or {@code null} if an invalid currency was set. + * @see #setCurrency(Currency) + * @see #setInternationalCurrencySymbol(String) + * @since Android 1.0 + */ + public Currency getCurrency() { + return currency; + } + + /** + * Returns the international currency symbol. + * + * @return the international currency symbol as string. + * @since Android 1.0 + */ + public String getInternationalCurrencySymbol() { + return intlCurrencySymbol; + } + + /** + * Returns the currency symbol. + * + * @return the currency symbol as string. + * @since Android 1.0 + */ + public String getCurrencySymbol() { + return currencySymbol; + } + + /** + * Returns the character which represents the decimal point in a number. + * + * @return the decimal separator character. + * @since Android 1.0 + */ + public char getDecimalSeparator() { + return patternChars[DecimalSeparator]; + } + + /** + * Returns the character which represents a single digit in a format + * pattern. + * + * @return the digit pattern character. + * @since Android 1.0 + */ + public char getDigit() { + return patternChars[Digit]; + } + + /** + * Returns the character used as the thousands separator in a number. + * + * @return the thousands separator character. + * @since Android 1.0 + */ + public char getGroupingSeparator() { + return patternChars[GroupingSeparator]; + } + + /** + * Returns the string which represents infinity. + * + * @return the infinity symbol as a string. + * @since Android 1.0 + */ + public String getInfinity() { + return infinity; + } + + /** + * Returns the minus sign character. + * + * @return the minus sign as a character. + * @since Android 1.0 + */ + public char getMinusSign() { + return patternChars[MinusSign]; + } + + /** + * Returns the character which represents the decimal point in a monetary + * value. + * + * @return the monetary decimal point as a character. + * @since Android 1.0 + */ + public char getMonetaryDecimalSeparator() { + return patternChars[MonetaryDecimalSeparator]; + } + + /** + * Returns the string which represents NaN. + * + * @return the symbol NaN as a string. + * @since Android 1.0 + */ + public String getNaN() { + return NaN; + } + + /** + * Returns the character which separates the positive and negative patterns + * in a format pattern. + * + * @return the pattern separator character. + * @since Android 1.0 + */ + public char getPatternSeparator() { + return patternChars[PatternSeparator]; + } + + /** + * Returns the percent character. + * + * @return the percent character. + * @since Android 1.0 + */ + public char getPercent() { + return patternChars[Percent]; + } + + /** + * Returns the per mill sign character. + * + * @return the per mill sign character. + * @since Android 1.0 + */ + public char getPerMill() { + return patternChars[PerMill]; + } + + /** + * Returns the character which represents zero. + * + * @return the zero character. + * @since Android 1.0 + */ + public char getZeroDigit() { + return patternChars[ZeroDigit]; + } + + /* + * Returns the exponent as a character. + */ + char getExponential() { + return patternChars[Exponent]; + } + + @Override + public int hashCode() { + return new String(patternChars).hashCode() + infinity.hashCode() + + NaN.hashCode() + currencySymbol.hashCode() + + intlCurrencySymbol.hashCode(); + } + + /** + * Sets the currency. + * <p> + * The international currency symbol and the currency symbol are updated, + * but the min and max number of fraction digits stays the same. + * <p> + * + * @param currency + * the new currency. + * @throws NullPointerException + * if {@code currency} is {@code null}. + * @since Android 1.0 + */ + public void setCurrency(Currency currency) { + if (currency == null) { + throw new NullPointerException(); + } + if (currency == this.currency) { + return; + } + this.currency = currency; + intlCurrencySymbol = currency.getCurrencyCode(); + currencySymbol = currency.getSymbol(locale); + } + + /** + * Sets the international currency symbol. + * <p> + * The currency and currency symbol are also updated if {@code value} is a + * valid ISO4217 currency code. + * <p> + * The min and max number of fraction digits stay the same. + * + * @param value + * the currency code. + * @since Android 1.0 + */ + public void setInternationalCurrencySymbol(String value) { + if (value == null) { + currency = null; + intlCurrencySymbol = null; + return; + } + + if (value.equals(intlCurrencySymbol)) { + return; + } + + try { + currency = Currency.getInstance(value); + currencySymbol = currency.getSymbol(locale); + } catch (IllegalArgumentException e) { + currency = null; + } + intlCurrencySymbol = value; + } + + /** + * Sets the currency symbol. + * + * @param value + * the currency symbol. + * @since Android 1.0 + */ + public void setCurrencySymbol(String value) { + currencySymbol = value; + } + + /** + * Sets the character which represents the decimal point in a number. + * + * @param value + * the decimal separator character. + * @since Android 1.0 + */ + public void setDecimalSeparator(char value) { + patternChars[DecimalSeparator] = value; + } + + /** + * Sets the character which represents a single digit in a format pattern. + * + * @param value + * the digit character. + * @since Android 1.0 + */ + public void setDigit(char value) { + patternChars[Digit] = value; + } + + /** + * Sets the character used as the thousands separator in a number. + * + * @param value + * the grouping separator character. + * @since Android 1.0 + */ + public void setGroupingSeparator(char value) { + patternChars[GroupingSeparator] = value; + } + + /** + * Sets the string which represents infinity. + * + * @param value + * the string representing infinity. + * @since Android 1.0 + */ + public void setInfinity(String value) { + infinity = value; + } + + /** + * Sets the minus sign character. + * + * @param value + * the minus sign character. + * @since Android 1.0 + */ + public void setMinusSign(char value) { + patternChars[MinusSign] = value; + } + + /** + * Sets the character which represents the decimal point in a monetary + * value. + * + * @param value + * the monetary decimal separator character. + * @since Android 1.0 + */ + public void setMonetaryDecimalSeparator(char value) { + patternChars[MonetaryDecimalSeparator] = value; + } + + /** + * Sets the string which represents NaN. + * + * @param value + * the string representing NaN. + * @since Android 1.0 + */ + public void setNaN(String value) { + NaN = value; + } + + /** + * Sets the character which separates the positive and negative patterns in + * a format pattern. + * + * @param value + * the pattern separator character. + * @since Android 1.0 + */ + public void setPatternSeparator(char value) { + patternChars[PatternSeparator] = value; + } + + /** + * Sets the percent character. + * + * @param value + * the percent character. + * @since Android 1.0 + */ + public void setPercent(char value) { + patternChars[Percent] = value; + } + + /** + * Sets the per mill sign character. + * + * @param value + * the per mill character. + * @since Android 1.0 + */ + public void setPerMill(char value) { + patternChars[PerMill] = value; + } + + /** + * Sets the character which represents zero. + * + * @param value + * the zero digit character. + * @since Android 1.0 + */ + public void setZeroDigit(char value) { + patternChars[ZeroDigit] = value; + } + + /* + * Sets the exponent character. + */ + void setExponential(char value) { + patternChars[Exponent] = value; + } + + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("currencySymbol", String.class), //$NON-NLS-1$ + new ObjectStreamField("decimalSeparator", Character.TYPE), //$NON-NLS-1$ + new ObjectStreamField("digit", Character.TYPE), //$NON-NLS-1$ + new ObjectStreamField("exponential", Character.TYPE), //$NON-NLS-1$ + new ObjectStreamField("groupingSeparator", Character.TYPE), //$NON-NLS-1$ + new ObjectStreamField("infinity", String.class), //$NON-NLS-1$ + new ObjectStreamField("intlCurrencySymbol", String.class), //$NON-NLS-1$ + new ObjectStreamField("minusSign", Character.TYPE), //$NON-NLS-1$ + new ObjectStreamField("monetarySeparator", Character.TYPE), //$NON-NLS-1$ + new ObjectStreamField("NaN", String.class), //$NON-NLS-1$ + new ObjectStreamField("patternSeparator", Character.TYPE), //$NON-NLS-1$ + new ObjectStreamField("percent", Character.TYPE), //$NON-NLS-1$ + new ObjectStreamField("perMill", Character.TYPE), //$NON-NLS-1$ + new ObjectStreamField("serialVersionOnStream", Integer.TYPE), //$NON-NLS-1$ + new ObjectStreamField("zeroDigit", Character.TYPE), //$NON-NLS-1$ + new ObjectStreamField("locale", Locale.class), }; //$NON-NLS-1$ + + private void writeObject(ObjectOutputStream stream) throws IOException { + ObjectOutputStream.PutField fields = stream.putFields(); + fields.put("currencySymbol", currencySymbol); //$NON-NLS-1$ + fields.put("decimalSeparator", getDecimalSeparator()); //$NON-NLS-1$ + fields.put("digit", getDigit()); //$NON-NLS-1$ + fields.put("exponential", getExponential()); //$NON-NLS-1$ + fields.put("groupingSeparator", getGroupingSeparator()); //$NON-NLS-1$ + fields.put("infinity", infinity); //$NON-NLS-1$ + fields.put("intlCurrencySymbol", intlCurrencySymbol); //$NON-NLS-1$ + fields.put("minusSign", getMinusSign()); //$NON-NLS-1$ + fields.put("monetarySeparator", getMonetaryDecimalSeparator()); //$NON-NLS-1$ + fields.put("NaN", NaN); //$NON-NLS-1$ + fields.put("patternSeparator", getPatternSeparator()); //$NON-NLS-1$ + fields.put("percent", getPercent()); //$NON-NLS-1$ + fields.put("perMill", getPerMill()); //$NON-NLS-1$ + fields.put("serialVersionOnStream", 1); //$NON-NLS-1$ + fields.put("zeroDigit", getZeroDigit()); //$NON-NLS-1$ + fields.put("locale", locale); //$NON-NLS-1$ + stream.writeFields(); + } + + private void readObject(ObjectInputStream stream) throws IOException, + ClassNotFoundException { + ObjectInputStream.GetField fields = stream.readFields(); + patternChars = new char[10]; + currencySymbol = (String) fields.get("currencySymbol", ""); //$NON-NLS-1$ //$NON-NLS-2$ + setDecimalSeparator(fields.get("decimalSeparator", '.')); //$NON-NLS-1$ + setDigit(fields.get("digit", '#')); //$NON-NLS-1$ + setGroupingSeparator(fields.get("groupingSeparator", ',')); //$NON-NLS-1$ + infinity = (String) fields.get("infinity", ""); //$NON-NLS-1$ //$NON-NLS-2$ + intlCurrencySymbol = (String) fields.get("intlCurrencySymbol", ""); //$NON-NLS-1$ //$NON-NLS-2$ + setMinusSign(fields.get("minusSign", '-')); //$NON-NLS-1$ + NaN = (String) fields.get("NaN", ""); //$NON-NLS-1$ //$NON-NLS-2$ + setPatternSeparator(fields.get("patternSeparator", ';')); //$NON-NLS-1$ + setPercent(fields.get("percent", '%')); //$NON-NLS-1$ + setPerMill(fields.get("perMill", '\u2030')); //$NON-NLS-1$ + setZeroDigit(fields.get("zeroDigit", '0')); //$NON-NLS-1$ + locale = (Locale) fields.get("locale", null); //$NON-NLS-1$ + if (fields.get("serialVersionOnStream", 0) == 0) { //$NON-NLS-1$ + setMonetaryDecimalSeparator(getDecimalSeparator()); + setExponential('E'); + } else { + setMonetaryDecimalSeparator(fields.get("monetarySeparator", '.')); //$NON-NLS-1$ + setExponential(fields.get("exponential", 'E')); //$NON-NLS-1$ + + } + try { + currency = Currency.getInstance(intlCurrencySymbol); + } catch (IllegalArgumentException e) { + currency = null; + } + } +} diff --git a/text/src/main/java/java/text/FieldPosition.java b/text/src/main/java/java/text/FieldPosition.java new file mode 100644 index 0000000..4cf985d --- /dev/null +++ b/text/src/main/java/java/text/FieldPosition.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.text; + +/** + * Identifies fields in formatted strings. If a {@code FieldPosition} is passed + * to the format method with such a parameter, then the indices will be set to + * the start and end indices of the field in the formatted string. + * <p> + * A {@code FieldPosition} can be created by using the integer constants in the + * various format classes (for example {@code NumberFormat.INTEGER_FIELD}) or + * one of the fields of type {@code Format.Field}. + * </p> + * <p> + * If more than one field information is needed, the method + * {@link NumberFormat#formatToCharacterIterator(Object)} should be used. + * </p> + * + * @since Android 1.0 + */ +public class FieldPosition { + + private int myField, beginIndex, endIndex; + + private Format.Field myAttribute; + + /** + * Constructs a new {@code FieldPosition} for the specified field. + * + * @param field + * the field to identify. + * @since Android 1.0 + */ + public FieldPosition(int field) { + myField = field; + } + + /** + * Constructs a new {@code FieldPosition} for the specified {@code Field} + * attribute. + * + * @param attribute + * the field attribute to identify. + * @since Android 1.0 + */ + public FieldPosition(Format.Field attribute) { + myAttribute = attribute; + myField = -1; + } + + /** + * Constructs a new {@code FieldPosition} for the specified {@code Field} + * attribute and field id. + * + * @param attribute + * the field attribute to identify. + * @param field + * the field to identify. + * @since Android 1.0 + */ + public FieldPosition(Format.Field attribute, int field) { + myAttribute = attribute; + myField = field; + } + + void clear() { + beginIndex = endIndex = 0; + } + + /** + * Compares the specified object to this field position and indicates if + * they are equal. In order to be equal, {@code object} must be an instance + * of {@code FieldPosition} with the same field, begin index and end index. + * + * @param object + * the object to compare with this object. + * @return {@code true} if the specified object is equal to this field + * position; {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof FieldPosition)) { + return false; + } + FieldPosition pos = (FieldPosition) object; + return myField == pos.myField && myAttribute == pos.myAttribute + && beginIndex == pos.beginIndex && endIndex == pos.endIndex; + } + + /** + * Returns the index of the beginning of the field. + * + * @return the first index of the field. + * @since Android 1.0 + */ + public int getBeginIndex() { + return beginIndex; + } + + /** + * Returns the index one past the end of the field. + * + * @return one past the index of the last character in the field. + * @since Android 1.0 + */ + public int getEndIndex() { + return endIndex; + } + + /** + * Returns the field which is being identified. + * + * @return the field constant. + * @since Android 1.0 + */ + public int getField() { + return myField; + } + + /** + * Returns the attribute which is being identified. + * + * @return the field. + * @since Android 1.0 + */ + public Format.Field getFieldAttribute() { + return myAttribute; + } + + @Override + public int hashCode() { + int attributeHash = (myAttribute == null) ? 0 : myAttribute.hashCode(); + return attributeHash + myField * 10 + beginIndex * 100 + endIndex; + } + + /** + * Sets the index of the beginning of the field. + * + * @param index + * the index of the first character in the field. + * @since Android 1.0 + */ + public void setBeginIndex(int index) { + beginIndex = index; + } + + /** + * Sets the index of the end of the field. + * + * @param index + * one past the index of the last character in the field. + * @since Android 1.0 + */ + public void setEndIndex(int index) { + endIndex = index; + } + + /** + * Returns the string representation of this field position. + * + * @return the string representation of this field position. + * @since Android 1.0 + */ + @Override + public String toString() { + return getClass().getName() + "[attribute=" + myAttribute + ", field=" //$NON-NLS-1$ //$NON-NLS-2$ + + myField + ", beginIndex=" + beginIndex + ", endIndex=" //$NON-NLS-1$ //$NON-NLS-2$ + + endIndex + "]"; //$NON-NLS-1$ + } +} diff --git a/text/src/main/java/java/text/Format.java b/text/src/main/java/java/text/Format.java new file mode 100644 index 0000000..5e31cd4 --- /dev/null +++ b/text/src/main/java/java/text/Format.java @@ -0,0 +1,328 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** +******************************************************************************* +* Copyright (C) 1996-2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc and some of the method descriptions are copied from ICU4J +// source files. Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// END android-note + +package java.text; + +import java.io.Serializable; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Locale; +import java.util.ResourceBundle; + +import org.apache.harmony.text.internal.nls.Messages; + +/** + * The base class for all formats. + * <p> + * This is an abstract base class which specifies the protocol for classes which + * convert other objects or values, such as numeric values and dates, and their + * string representations. In some cases these representations may be localized + * or contain localized characters or strings. For example, a numeric formatter + * such as {@code DecimalFormat} may convert a numeric value such as 12345 to + * the string "$12,345". It may also parse the string back into a numeric value. + * A date and time formatter like {@code SimpleDateFormat} may represent a + * specific date, encoded numerically, as a string such as "Wednesday, February + * 26, 1997 AD". + * </p> + * <p> + * Many of the concrete subclasses of {@code Format} employ the notion of a + * pattern. A pattern is a string representation of the rules which govern the + * conversion between values and strings. For example, a {@code DecimalFormat} + * object may be associated with the pattern "$#,##0.00;($#,##0.00)", which is a + * common US English format for currency values, yielding strings such as + * "$1,234.45" for 1234.45, and "($987.65)" for -987.6543. The specific syntax + * of a pattern is defined by each subclass. Even though many subclasses use + * patterns, the notion of a pattern is not inherent to {@code Format} classes + * in general, and is not part of the explicit base class protocol. + * </p> + * <p> + * Two complex formatting classes are worth mentioning: {@code MessageFormat} + * and {@code ChoiceFormat}. {@code ChoiceFormat} is a subclass of + * {@code NumberFormat} which allows the user to format different number ranges + * as strings. For instance, 0 may be represented as "no files", 1 as "one + * file", and any number greater than 1 as "many files". {@code MessageFormat} + * is a formatter which utilizes other {@code Format} objects to format a string + * containing multiple values. For instance, a {@code MessageFormat} object + * might produce the string "There are no files on the disk MyDisk on February + * 27, 1997." given the arguments 0, "MyDisk", and the date value of 2/27/97. + * See the {@link ChoiceFormat} and {@link MessageFormat} descriptions for + * further information. + * </p> + * + * @since Android 1.0 + */ +public abstract class Format implements Serializable, Cloneable { + + private static final long serialVersionUID = -299282585814624189L; + + /** + * Constructs a new {@code Format} instance. + * + * @since Android 1.0 + */ + public Format() { + } + + /** + * Returns a copy of this {@code Format} instance. + * + * @return a shallow copy of this format. + * + * @see java.lang.Cloneable + * @since Android 1.0 + */ + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + static ResourceBundle getBundle(final Locale locale) { + return AccessController + .doPrivileged(new PrivilegedAction<ResourceBundle>() { + public ResourceBundle run() { + return ResourceBundle + .getBundle( + "org.apache.harmony.luni.internal.locale.Locale", locale); //$NON-NLS-1$ + } + }); + } + + String convertPattern(String template, String fromChars, String toChars, + boolean check) { + if (!check && fromChars.equals(toChars)) { + return template; + } + boolean quote = false; + StringBuilder output = new StringBuilder(); + int length = template.length(); + for (int i = 0; i < length; i++) { + int index; + char next = template.charAt(i); + if (next == '\'') { + quote = !quote; + } + if (!quote && (index = fromChars.indexOf(next)) != -1) { + output.append(toChars.charAt(index)); + } else if (check + && !quote + && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) { + // text.05=Invalid pattern char {0} in {1} + throw new IllegalArgumentException(Messages.getString( + "text.05", String.valueOf(next), template)); //$NON-NLS-1$ + } else { + output.append(next); + } + } + if (quote) { + // text.04=Unterminated quote + throw new IllegalArgumentException(Messages.getString("text.04")); //$NON-NLS-1$ + } + return output.toString(); + } + + /** + * Formats the specified object using the rules of this format. + * + * @param object + * the object to format. + * @return the formatted string. + * @exception IllegalArgumentException + * if the object cannot be formatted by this format. + * @since Android 1.0 + */ + public final String format(Object object) { + return format(object, new StringBuffer(), new FieldPosition(0)) + .toString(); + } + + /** + * Appends the specified object to the specified string buffer using the + * rules of this format. + * <p> + * {@code field} is an input/output parameter. If its {@code field} + * member contains an enum value specifying a field on input, then its + * {@code beginIndex} and {@code endIndex} members will be updated with the + * text offset of the first occurrence of this field in the formatted text. + * </p> + * + * @param object + * the object to format. + * @param buffer + * the string buffer where the formatted string is appended to. + * @param field + * on input: an optional alignment field; on output: the offsets + * of the alignment field in the formatted text. + * @return the string buffer. + * @exception IllegalArgumentException + * if the object cannot be formatted by this format. + * @since Android 1.0 + */ + public abstract StringBuffer format(Object object, StringBuffer buffer, + FieldPosition field); + + /** + * Formats the specified object using the rules of this format and returns + * an {@code AttributedCharacterIterator} with the formatted string and no + * attributes. + * <p> + * Subclasses should return an {@code AttributedCharacterIterator} with the + * appropriate attributes. + * </p> + * + * @param object + * the object to format. + * @return an {@code AttributedCharacterIterator} with the formatted object + * and attributes. + * @exception IllegalArgumentException + * if the object cannot be formatted by this format. + * @since Android 1.0 + */ + public AttributedCharacterIterator formatToCharacterIterator(Object object) { + return new AttributedString(format(object)).getIterator(); + } + + /** + * Parses the specified string using the rules of this format. + * + * @param string + * the string to parse. + * @return the object resulting from the parse. + * @exception ParseException + * if an error occurs during parsing. + * @since Android 1.0 + */ + public Object parseObject(String string) throws ParseException { + ParsePosition position = new ParsePosition(0); + Object result = parseObject(string, position); + if (position.getErrorIndex() != -1 || position.getIndex() == 0) { + throw new ParseException(null, position.getErrorIndex()); + } + return result; + } + + /** + * Parses the specified string starting at the index specified by + * {@code position}. If the string is successfully parsed then the index of + * the {@code ParsePosition} is updated to the index following the parsed + * text. On error, the index is unchanged and the error index of + * {@code ParsePosition} is set to the index where the error occurred. + * + * @param string + * the string to parse. + * @param position + * input/output parameter, specifies the start index in + * {@code string} from where to start parsing. If parsing is + * successful, it is updated with the index following the parsed + * text; on error, the index is unchanged and the error index is + * set to the index where the error occurred. + * @return the object resulting from the parse or {@code null} if there is + * an error. + * @since Android 1.0 + */ + public abstract Object parseObject(String string, ParsePosition position); + + static boolean upTo(String string, ParsePosition position, + StringBuffer buffer, char stop) { + int index = position.getIndex(), length = string.length(); + boolean lastQuote = false, quote = false; + while (index < length) { + char ch = string.charAt(index++); + if (ch == '\'') { + if (lastQuote) { + buffer.append('\''); + } + quote = !quote; + lastQuote = true; + } else if (ch == stop && !quote) { + position.setIndex(index); + return true; + } else { + lastQuote = false; + buffer.append(ch); + } + } + position.setIndex(index); + return false; + } + + static boolean upToWithQuotes(String string, ParsePosition position, + StringBuffer buffer, char stop, char start) { + int index = position.getIndex(), length = string.length(), count = 1; + boolean quote = false; + while (index < length) { + char ch = string.charAt(index++); + if (ch == '\'') { + quote = !quote; + } + if (!quote) { + if (ch == stop) { + count--; + } + if (count == 0) { + position.setIndex(index); + return true; + } + if (ch == start) { + count++; + } + } + buffer.append(ch); + } + // text.07=Unmatched braces in the pattern + throw new IllegalArgumentException(Messages.getString("text.07")); //$NON-NLS-1$ + } + + /** + * Inner class used to represent {@code Format} attributes in the + * {@code AttributedCharacterIterator} that the + * {@code formatToCharacterIterator()} method returns in {@code Format} + * subclasses. + * + * @since Android 1.0 + */ + public static class Field extends AttributedCharacterIterator.Attribute { + + private static final long serialVersionUID = 276966692217360283L; + + /** + * Constructs a new instance of {@code Field} with the given field name. + * + * @param fieldName + * the field name. + * @since Android 1.0 + */ + protected Field(String fieldName) { + super(fieldName); + } + } +} diff --git a/text/src/main/java/java/text/MessageFormat.java b/text/src/main/java/java/text/MessageFormat.java new file mode 100644 index 0000000..6405b6c --- /dev/null +++ b/text/src/main/java/java/text/MessageFormat.java @@ -0,0 +1,1412 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** +******************************************************************************* +* Copyright (C) 1996-2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc and some of the method descriptions are copied from ICU4J +// source files. Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// END android-note + +package java.text; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.Locale; +import java.util.Vector; + +import org.apache.harmony.text.internal.nls.Messages; + +/** + * Produces concatenated + * messages in language-neutral way. Use this class to construct messages + * displayed for end users. + * <p> + * {@code MessageFormat} takes a set of objects, formats them and then + * inserts the formatted strings into the pattern at the appropriate places. + * </p> + * <p> + * <strong>Note:</strong> {@code MessageFormat} differs from the other + * {@code Format} classes in that you create a {@code MessageFormat} + * object with one of its constructors (not with a {@code getInstance} + * style factory method). The factory methods aren't necessary because + * {@code MessageFormat} itself doesn't implement locale specific + * behavior. Any locale specific behavior is defined by the pattern that you + * provide as well as the subformats used for inserted arguments. + * + * <h4><a name="patterns">Patterns and their interpretation</a></h4> + * + * {@code MessageFormat} uses patterns of the following form: + * <blockquote> + * + * <pre> + * <i>MessageFormatPattern:</i> + * <i>String</i> + * <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i> + * <i>FormatElement:</i> + * { <i>ArgumentIndex</i> } + * { <i>ArgumentIndex</i> , <i>FormatType</i> } + * { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> } + * <i>FormatType: one of </i> + * number date time choice + * <i>FormatStyle:</i> + * short + * medium + * long + * full + * integer + * currency + * percent + * <i>SubformatPattern</i> + * <i>String:</i> + * <i>StringPart<sub>opt</sub></i> + * <i>String</i> <i>StringPart</i> + * <i>StringPart:</i> + * '' + * ' <i>QuotedString</i> ' + * <i>UnquotedString</i> + * <i>SubformatPattern:</i> + * <i>SubformatPatternPart<sub>opt</sub></i> + * <i>SubformatPattern</i> <i>SubformatPatternPart</i> + * <i>SubFormatPatternPart:</i> + * ' <i>QuotedPattern</i> ' + * <i>UnquotedPattern</i> + * </pre> + * + * </blockquote> + * + * <p> + * Within a <i>String</i>, {@code "''"} represents a single quote. A + * <i>QuotedString</i> can contain arbitrary characters except single quotes; + * the surrounding single quotes are removed. An <i>UnquotedString</i> can + * contain arbitrary characters except single quotes and left curly brackets. + * Thus, a string that should result in the formatted message "'{0}'" can be + * written as {@code "'''{'0}''"} or {@code "'''{0}'''"}. + * <p> + * Within a <i>SubformatPattern</i>, different rules apply. A <i>QuotedPattern</i> + * can contain arbitrary characters except single quotes, but the surrounding + * single quotes are <strong>not</strong> removed, so they may be interpreted + * by the subformat. For example, {@code "{1,number,$'#',##}"} will + * produce a number format with the hash-sign quoted, with a result such as: + * "$#31,45". An <i>UnquotedPattern</i> can contain arbitrary characters except + * single quotes, but curly braces within it must be balanced. For example, + * {@code "ab {0} de"} and {@code "ab '}' de"} are valid subformat + * patterns, but {@code "ab {0'}' de"} and {@code "ab } de"} are + * not. + * </p> + * <dl> + * <dt><b>Warning:</b></dt> + * <dd>The rules for using quotes within message format patterns unfortunately + * have shown to be somewhat confusing. In particular, it isn't always obvious + * to localizers whether single quotes need to be doubled or not. Make sure to + * inform localizers about the rules, and tell them (for example, by using + * comments in resource bundle source files) which strings will be processed by + * {@code MessageFormat}. Note that localizers may need to use single quotes in + * translated strings where the original version doesn't have them. <br> + * Note also that the simplest way to avoid the problem is to use the real + * apostrophe (single quote) character \u2019 (') for human-readable text, and + * to use the ASCII apostrophe (\u0027 ' ) only in program syntax, like quoting + * in {@code MessageFormat}. See the annotations for U+0027 Apostrophe in The Unicode + * Standard. + * </dl> + * <p> + * The <i>ArgumentIndex</i> value is a non-negative integer written using the + * digits '0' through '9', and represents an index into the + * {@code arguments} array passed to the {@code format} methods or + * the result array returned by the {@code parse} methods. + * <p> + * The <i>FormatType</i> and <i>FormatStyle</i> values are used to create a + * {@code Format} instance for the format element. The following table + * shows how the values map to {@code Format} instances. Combinations not shown in the + * table are illegal. A <i>SubformatPattern</i> must be a valid pattern string + * for the {@code Format} subclass used. + * <p> + * <table border=1> + * <tr> + * <th>Format Type</th> + * <th>Format Style</th> + * <th>Subformat Created</th> + * </tr> + * <tr> + * <td colspan="2"><i>(none)</i></td> + * <td>{@code null}</td> + * </tr> + * <tr> + * <td rowspan="5">{@code number}</td> + * <td><i>(none)</i></td> + * <td>{@code NumberFormat.getInstance(getLocale())}</td> + * </tr> + * <tr> + * <td>{@code integer}</td> + * <td>{@code NumberFormat.getIntegerInstance(getLocale())}</td> + * </tr> + * <tr> + * <td>{@code currency}</td> + * <td>{@code NumberFormat.getCurrencyInstance(getLocale())}</td> + * </tr> + * <tr> + * <td>{@code percent}</td> + * <td>{@code NumberFormat.getPercentInstance(getLocale())}</td> + * </tr> + * <tr> + * <td><i>SubformatPattern</i></td> + * <td>{@code new DecimalFormat(subformatPattern, new DecimalFormatSymbols(getLocale()))}</td> + * </tr> + * <tr> + * <td rowspan="6">{@code date}</td> + * <td><i>(none)</i></td> + * <td>{@code DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())}</td> + * </tr> + * <tr> + * <td>{@code short}</td> + * <td>{@code DateFormat.getDateInstance(DateFormat.SHORT, getLocale())}</td> + * </tr> + * <tr> + * <td>{@code medium}</td> + * <td>{@code DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())}</td> + * </tr> + * <tr> + * <td>{@code long}</td> + * <td>{@code DateFormat.getDateInstance(DateFormat.LONG, getLocale())}</td> + * </tr> + * <tr> + * <td>{@code full}</td> + * <td>{@code DateFormat.getDateInstance(DateFormat.FULL, getLocale())}</td> + * </tr> + * <tr> + * <td><i>SubformatPattern</i></td> + * <td>{@code new SimpleDateFormat(subformatPattern, getLocale())}</td> + * </tr> + * <tr> + * <td rowspan="6">{@code time}</td> + * <td><i>(none)</i></td> + * <td>{@code DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())}</td> + * </tr> + * <tr> + * <td>{@code short}</td> + * <td>{@code DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())}</td> + * </tr> + * <tr> + * <td>{@code medium}</td> + * <td>{@code DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())}</td> + * </tr> + * <tr> + * <td>{@code long}</td> + * <td>{@code DateFormat.getTimeInstance(DateFormat.LONG, getLocale())}</td> + * </tr> + * <tr> + * <td>{@code full}</td> + * <td>{@code DateFormat.getTimeInstance(DateFormat.FULL, getLocale())}</td> + * </tr> + * <tr> + * <td><i>SubformatPattern</i></td> + * <td>{@code new SimpleDateFormat(subformatPattern, getLocale())}</td> + * </tr> + * <tr> + * <td>{@code choice}</td> + * <td><i>SubformatPattern</i></td> + * <td>{@code new ChoiceFormat(subformatPattern)}</td> + * </tr> + * </table> + * + * <h4>Usage Information</h4> + * <p> + * Here are some examples of usage: <blockquote> + * + * <pre> + * Object[] arguments = { + * new Integer(7), new Date(System.currentTimeMillis()), + * "a disturbance in the Force"}; + * String result = MessageFormat.format( + * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.", + * arguments); + * <em> + * Output: + * </em> + * At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7. + * </pre> + * + * </blockquote> + * <p> + * Typically, the message format will come from resources, and the + * arguments will be dynamically set at runtime. + * </p> + * <p> + * Example 2: <blockquote> + * + * <pre> + * Object[] testArgs = {new Long(3), "MyDisk"}; + * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0} file(s)."); + * System.out.println(form.format(testArgs)); + * <em> + * Output with different testArgs: + * </em> + * The disk "MyDisk" contains 0 file(s). + * The disk "MyDisk" contains 1 file(s). + * The disk "MyDisk" contains 1,273 file(s). + * </pre> + * + * </blockquote> + * + * <p> + * For more sophisticated patterns, you can use a {@code ChoiceFormat} to + * get output such as: + * </p> + * <blockquote> + * + * <pre> + * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}."); + * double[] filelimits = {0,1,2}; + * String[] filepart = {"no files","one file","{0,number} files"}; + * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart); + * form.setFormatByArgumentIndex(0, fileform); + * Object[] testArgs = {new Long(12373), "MyDisk"}; + * System.out.println(form.format(testArgs)); + * <em> + * Output (with different testArgs): + * </em> + * The disk "MyDisk" contains no files. + * The disk "MyDisk" contains one file. + * The disk "MyDisk" contains 1,273 files. + * </pre> + * + * </blockquote> You can either do this programmatically, as in the above + * example, or by using a pattern (see {@link ChoiceFormat} for more + * information) as in: <blockquote> + * + * <pre> + * form.applyPattern("There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}."); + * </pre> + * + * </blockquote> + * <p> + * <strong>Note:</strong> As we see above, the string produced by a + * {@code ChoiceFormat} in {@code MessageFormat} is treated + * specially; occurances of '{' are used to indicated subformats, and cause + * recursion. If you create both a {@code MessageFormat} and + * {@code ChoiceFormat} programmatically (instead of using the string + * patterns), then be careful not to produce a format that recurses on itself, + * which will cause an infinite loop. + * </p> + * <p> + * When a single argument is parsed more than once in the string, the last match + * will be the final result of the parsing. For example: + * </p> + * <blockquote> + * <pre> + * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}"); + * Object[] objs = {new Double(3.1415)}; + * String result = mf.format(objs); + * // result now equals "3.14, 3.1" + * objs = null; + * objs = mf.parse(result, new ParsePosition(0)); + * // objs now equals {new Double(3.1)} + * </pre> + * </blockquote> + * <p> + * Likewise, parsing with a {@code MessageFormat} object using patterns + * containing multiple occurrences of the same argument would return the last + * match. For example: + * </p> + * <blockquote> + * <pre> + * MessageFormat mf = new MessageFormat("{0}, {0}, {0}"); + * String forParsing = "x, y, z"; + * Object[] objs = mf.parse(forParsing, new ParsePosition(0)); + * // result now equals {new String("z")} + * </pre> + * </blockquote> + * <h4><a name="synchronization">Synchronization</a></h4> + * <p> + * Message formats are not synchronized. It is recommended to create separate + * format instances for each thread. If multiple threads access a format + * concurrently, it must be synchronized externally. + * </p> + * + * @see java.util.Locale + * @see Format + * @see NumberFormat + * @see DecimalFormat + * @see ChoiceFormat + * @since Android 1.0 + */ +public class MessageFormat extends Format { + + private static final long serialVersionUID = 6479157306784022952L; + + private Locale locale = Locale.getDefault(); + + transient private String[] strings; + + private int[] argumentNumbers; + + private Format[] formats; + + private int maxOffset; + + transient private int maxArgumentIndex; + + /** + * Constructs a new {@code MessageFormat} using the specified pattern and + * the specified locale for formats. + * + * @param template + * the pattern. + * @param locale + * the locale. + * @exception IllegalArgumentException + * if the pattern cannot be parsed. + * @since Android 1.0 + */ + public MessageFormat(String template, Locale locale) { + this.locale = locale; + applyPattern(template); + } + + /** + * Constructs a new {@code MessageFormat} using the specified pattern and + * the default locale for formats. + * + * @param template + * the pattern. + * @exception IllegalArgumentException + * if the pattern cannot be parsed. + * @since Android 1.0 + */ + public MessageFormat(String template) { + applyPattern(template); + } + + /** + * Changes this {@code MessageFormat} to use the specified pattern. + * + * @param template + * the new pattern. + * @exception IllegalArgumentException + * if the pattern cannot be parsed. + * @since Android 1.0 + */ + public void applyPattern(String template) { + int length = template.length(); + StringBuffer buffer = new StringBuffer(); + ParsePosition position = new ParsePosition(0); + Vector<String> localStrings = new Vector<String>(); + int argCount = 0; + int[] args = new int[10]; + int maxArg = -1; + Vector<Format> localFormats = new Vector<Format>(); + while (position.getIndex() < length) { + if (Format.upTo(template, position, buffer, '{')) { + byte arg; + int offset = position.getIndex(); + if (offset >= length + || (arg = (byte) Character.digit(template + .charAt(offset++), 10)) == -1) { + // text.19=Invalid argument number + throw new IllegalArgumentException(Messages + .getString("text.19")); //$NON-NLS-1$ + } + position.setIndex(offset); + localFormats.addElement(parseVariable(template, position)); + if (argCount >= args.length) { + int[] newArgs = new int[args.length * 2]; + System.arraycopy(args, 0, newArgs, 0, args.length); + args = newArgs; + } + args[argCount++] = arg; + if (arg > maxArg) { + maxArg = arg; + } + } + localStrings.addElement(buffer.toString()); + buffer.setLength(0); + } + this.strings = new String[localStrings.size()]; + for (int i = 0; i < localStrings.size(); i++) { + this.strings[i] = localStrings.elementAt(i); + } + argumentNumbers = args; + this.formats = new Format[argCount]; + for (int i = 0; i < argCount; i++) { + this.formats[i] = localFormats.elementAt(i); + } + maxOffset = argCount - 1; + maxArgumentIndex = maxArg; + } + + /** + * Returns a new instance of {@code MessageFormat} with the same pattern and + * formats as this {@code MessageFormat}. + * + * @return a shallow copy of this {@code MessageFormat}. + * @see java.lang.Cloneable + * @since Android 1.0 + */ + @Override + public Object clone() { + MessageFormat clone = (MessageFormat) super.clone(); + Format[] array = new Format[formats.length]; + for (int i = formats.length; --i >= 0;) { + if (formats[i] != null) { + array[i] = (Format) formats[i].clone(); + } + } + clone.formats = array; + return clone; + } + + /** + * Compares the specified object to this {@code MessageFormat} and indicates + * if they are equal. In order to be equal, {@code object} must be an + * instance of {@code MessageFormat} and have the same pattern. + * + * @param object + * the object to compare with this object. + * @return {@code true} if the specified object is equal to this + * {@code MessageFormat}; {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof MessageFormat)) { + return false; + } + MessageFormat format = (MessageFormat) object; + if (maxOffset != format.maxOffset) { + return false; + } + // Must use a loop since the lengths may be different due + // to serialization cross-loading + for (int i = 0; i <= maxOffset; i++) { + if (argumentNumbers[i] != format.argumentNumbers[i]) { + return false; + } + } + return locale.equals(format.locale) + && Arrays.equals(strings, format.strings) + && Arrays.equals(formats, format.formats); + } + + /** + * Formats the specified object using the rules of this message format and + * returns an {@code AttributedCharacterIterator} with the formatted message and + * attributes. The {@code AttributedCharacterIterator} returned also includes the + * attributes from the formats of this message format. + * + * @param object + * the object to format. + * @return an {@code AttributedCharacterIterator} with the formatted message and + * attributes. + * @exception IllegalArgumentException + * if the arguments in the object array cannot be formatted + * by this message format. + * @since Android 1.0 + */ + @Override + public AttributedCharacterIterator formatToCharacterIterator(Object object) { + if (object == null) { + throw new NullPointerException(); + } + + StringBuffer buffer = new StringBuffer(); + Vector<FieldContainer> fields = new Vector<FieldContainer>(); + + // format the message, and find fields + formatImpl((Object[]) object, buffer, new FieldPosition(0), fields); + + // create an AttributedString with the formatted buffer + AttributedString as = new AttributedString(buffer.toString()); + + // add MessageFormat field attributes and values to the AttributedString + for (int i = 0; i < fields.size(); i++) { + FieldContainer fc = fields.elementAt(i); + as.addAttribute(fc.attribute, fc.value, fc.start, fc.end); + } + + // return the CharacterIterator from AttributedString + return as.getIterator(); + } + + /** + * Converts the specified objects into a string which it appends to the + * specified string buffer using the pattern of this message format. + * <p> + * If the {@code field} member of the specified {@code FieldPosition} is + * {@code MessageFormat.Field.ARGUMENT}, then the begin and end index of + * this field position is set to the location of the first occurrence of a + * message format argument. Otherwise, the {@code FieldPosition} is ignored. + * </p> + * + * @param objects + * the array of objects to format. + * @param buffer + * the target string buffer to append the formatted message to. + * @param field + * on input: an optional alignment field; on output: the offsets + * of the alignment field in the formatted text. + * @return the string buffer. + * @since Android 1.0 + */ + public final StringBuffer format(Object[] objects, StringBuffer buffer, + FieldPosition field) { + return formatImpl(objects, buffer, field, null); + } + + private StringBuffer formatImpl(Object[] objects, StringBuffer buffer, + FieldPosition position, Vector<FieldContainer> fields) { + FieldPosition passedField = new FieldPosition(0); + for (int i = 0; i <= maxOffset; i++) { + buffer.append(strings[i]); + int begin = buffer.length(); + Object arg; + if (objects != null && argumentNumbers[i] < objects.length) { + arg = objects[argumentNumbers[i]]; + } else { + buffer.append('{'); + buffer.append(argumentNumbers[i]); + buffer.append('}'); + handleArgumentField(begin, buffer.length(), argumentNumbers[i], + position, fields); + continue; + } + Format format = formats[i]; + if (format == null || arg == null) { + if (arg instanceof Number) { + format = NumberFormat.getInstance(); + } else if (arg instanceof Date) { + format = DateFormat.getInstance(); + } else { + buffer.append(arg); + handleArgumentField(begin, buffer.length(), + argumentNumbers[i], position, fields); + continue; + } + } + if (format instanceof ChoiceFormat) { + String result = format.format(arg); + MessageFormat mf = new MessageFormat(result); + mf.setLocale(locale); + mf.format(objects, buffer, passedField); + handleArgumentField(begin, buffer.length(), argumentNumbers[i], + position, fields); + handleformat(format, arg, begin, fields); + } else { + format.format(arg, buffer, passedField); + handleArgumentField(begin, buffer.length(), argumentNumbers[i], + position, fields); + handleformat(format, arg, begin, fields); + } + } + if (maxOffset + 1 < strings.length) { + buffer.append(strings[maxOffset + 1]); + } + return buffer; + } + + /** + * Adds a new FieldContainer with MessageFormat.Field.ARGUMENT field, + * argnumber, begin and end index to the fields vector, or sets the + * position's begin and end index if it has MessageFormat.Field.ARGUMENT as + * its field attribute. + * + * @param begin + * @param end + * @param argnumber + * @param position + * @param fields + */ + private void handleArgumentField(int begin, int end, int argnumber, + FieldPosition position, Vector<FieldContainer> fields) { + if (fields != null) { + fields.add(new FieldContainer(begin, end, Field.ARGUMENT, + new Integer(argnumber))); + } else { + if (position != null + && position.getFieldAttribute() == Field.ARGUMENT + && position.getEndIndex() == 0) { + position.setBeginIndex(begin); + position.setEndIndex(end); + } + } + } + + /** + * An inner class to store attributes, values, start and end indices. + * Instances of this inner class are used as elements for the fields vector + */ + private static class FieldContainer { + int start, end; + + AttributedCharacterIterator.Attribute attribute; + + Object value; + + public FieldContainer(int start, int end, + AttributedCharacterIterator.Attribute attribute, Object value) { + this.start = start; + this.end = end; + this.attribute = attribute; + this.value = value; + } + } + + /** + * If fields vector is not null, find and add the fields of this format to + * the fields vector by iterating through its AttributedCharacterIterator + * + * @param format + * the format to find fields for + * @param arg + * object to format + * @param begin + * the index where the string this format has formatted begins + * @param fields + * fields vector, each entry in this vector are of type + * FieldContainer. + */ + private void handleformat(Format format, Object arg, int begin, + Vector<FieldContainer> fields) { + if (fields != null) { + AttributedCharacterIterator iterator = format + .formatToCharacterIterator(arg); + while (iterator.getIndex() != iterator.getEndIndex()) { + int start = iterator.getRunStart(); + int end = iterator.getRunLimit(); + + Iterator<?> it = iterator.getAttributes().keySet().iterator(); + while (it.hasNext()) { + AttributedCharacterIterator.Attribute attribute = (AttributedCharacterIterator.Attribute) it + .next(); + Object value = iterator.getAttribute(attribute); + fields.add(new FieldContainer(begin + start, begin + end, + attribute, value)); + } + iterator.setIndex(end); + } + } + } + + /** + * Converts the specified objects into a string which it appends to the + * specified string buffer using the pattern of this message format. + * <p> + * If the {@code field} member of the specified {@code FieldPosition} is + * {@code MessageFormat.Field.ARGUMENT}, then the begin and end index of + * this field position is set to the location of the first occurrence of a + * message format argument. Otherwise, the {@code FieldPosition} is ignored. + * </p> + * <p> + * Calling this method is equivalent to calling + * </p> + * <blockquote> + * + * <pre> + * format((Object[])object, buffer, field) + * </pre> + * + * </blockquote> + * + * @param object + * the object to format, must be an array of {@code Object}. + * @param buffer + * the target string buffer to append the formatted message to. + * @param field + * on input: an optional alignment field; on output: the offsets + * of the alignment field in the formatted text. + * @return the string buffer. + * @throws ClassCastException + * if {@code object} is not an array of {@code Object}. + * @since Android 1.0 + */ + @Override + public final StringBuffer format(Object object, StringBuffer buffer, + FieldPosition field) { + return format((Object[]) object, buffer, field); + } + + /** + * Formats the supplied objects using the specified message format pattern. + * + * @param template + * the pattern to use for formatting. + * @param objects + * the array of objects to format. + * @return the formatted result. + * @exception IllegalArgumentException + * if the pattern cannot be parsed. + * @since Android 1.0 + */ + public static String format(String template, Object... objects) { + // BEGIN android-note + // changed parameter type from array to varargs. + // END android-note + return new MessageFormat(template).format(objects); + } + + /** + * Returns the {@code Format} instances used by this message format. + * + * @return an array of {@code Format} instances. + * @since Android 1.0 + */ + public Format[] getFormats() { + return formats.clone(); + } + + /** + * Returns the formats used for each argument index. If an argument is + * placed more than once in the pattern string, then this returns the format + * of the last one. + * + * @return an array of formats, ordered by argument index. + * @since Android 1.0 + */ + public Format[] getFormatsByArgumentIndex() { + Format[] answer = new Format[maxArgumentIndex + 1]; + for (int i = 0; i < maxOffset + 1; i++) { + answer[argumentNumbers[i]] = formats[i]; + } + return answer; + } + + /** + * Sets the format used for the argument at index {@code argIndex} to + * {@code format}. + * + * @param argIndex + * the index of the format to set. + * @param format + * the format that will be set at index {@code argIndex}. + * @since Android 1.0 + */ + public void setFormatByArgumentIndex(int argIndex, Format format) { + for (int i = 0; i < maxOffset + 1; i++) { + if (argumentNumbers[i] == argIndex) { + formats[i] = format; + } + } + } + + /** + * Sets the formats used for each argument. The {@code formats} array + * elements should be in the order of the argument indices. + * + * @param formats + * the formats in an array. + * @since Android 1.0 + */ + public void setFormatsByArgumentIndex(Format[] formats) { + for (int j = 0; j < formats.length; j++) { + for (int i = 0; i < maxOffset + 1; i++) { + if (argumentNumbers[i] == j) { + this.formats[i] = formats[j]; + } + } + } + } + + /** + * Returns the locale used when creating formats. + * + * @return the locale used to create formats. + * @since Android 1.0 + */ + public Locale getLocale() { + return locale; + } + + @Override + public int hashCode() { + int hashCode = 0; + for (int i = 0; i <= maxOffset; i++) { + hashCode += argumentNumbers[i] + strings[i].hashCode(); + if (formats[i] != null) { + hashCode += formats[i].hashCode(); + } + } + if (maxOffset + 1 < strings.length) { + hashCode += strings[maxOffset + 1].hashCode(); + } + if (locale != null) { + return hashCode + locale.hashCode(); + } + return hashCode; + } + + /** + * Parses the message arguments from the specified string using the rules of + * this message format. + * + * @param string + * the string to parse. + * @return the array of {@code Object} arguments resulting from the parse. + * @exception ParseException + * if an error occurs during parsing. + * @since Android 1.0 + */ + public Object[] parse(String string) throws ParseException { + ParsePosition position = new ParsePosition(0); + Object[] result = parse(string, position); + if (position.getErrorIndex() != -1 || position.getIndex() == 0) { + throw new ParseException(null, position.getErrorIndex()); + } + return result; + } + + /** + * Parses the message argument from the specified string starting at the + * index specified by {@code position}. If the string is successfully + * parsed then the index of the {@code ParsePosition} is updated to the + * index following the parsed text. On error, the index is unchanged and the + * error index of {@code ParsePosition} is set to the index where the error + * occurred. + * + * @param string + * the string to parse. + * @param position + * input/output parameter, specifies the start index in + * {@code string} from where to start parsing. If parsing is + * successful, it is updated with the index following the parsed + * text; on error, the index is unchanged and the error index is + * set to the index where the error occurred. + * @return the array of objects resulting from the parse, or {@code null} if + * there is an error. + * @since Android 1.0 + */ + public Object[] parse(String string, ParsePosition position) { + if (string == null) { + return new Object[0]; + } + ParsePosition internalPos = new ParsePosition(0); + int offset = position.getIndex(); + Object[] result = new Object[maxArgumentIndex + 1]; + for (int i = 0; i <= maxOffset; i++) { + String sub = strings[i]; + if (!string.startsWith(sub, offset)) { + position.setErrorIndex(offset); + return null; + } + offset += sub.length(); + Object parse; + Format format = formats[i]; + if (format == null) { + if (i + 1 < strings.length) { + int next = string.indexOf(strings[i + 1], offset); + if (next == -1) { + position.setErrorIndex(offset); + return null; + } + parse = string.substring(offset, next); + offset = next; + } else { + parse = string.substring(offset); + offset = string.length(); + } + } else { + internalPos.setIndex(offset); + parse = format.parseObject(string, internalPos); + if (internalPos.getErrorIndex() != -1) { + position.setErrorIndex(offset); + return null; + } + offset = internalPos.getIndex(); + } + result[argumentNumbers[i]] = parse; + } + if (maxOffset + 1 < strings.length) { + String sub = strings[maxOffset + 1]; + if (!string.startsWith(sub, offset)) { + position.setErrorIndex(offset); + return null; + } + offset += sub.length(); + } + position.setIndex(offset); + return result; + } + + /** + * Parses the message argument from the specified string starting at the + * index specified by {@code position}. If the string is successfully + * parsed then the index of the {@code ParsePosition} is updated to the + * index following the parsed text. On error, the index is unchanged and the + * error index of {@code ParsePosition} is set to the index where the error + * occurred. + * + * @param string + * the string to parse. + * @param position + * input/output parameter, specifies the start index in + * {@code string} from where to start parsing. If parsing is + * successful, it is updated with the index following the parsed + * text; on error, the index is unchanged and the error index is + * set to the index where the error occurred. + * @return the array of objects resulting from the parse, or {@code null} if + * there is an error. + * @since Android 1.0 + */ + @Override + public Object parseObject(String string, ParsePosition position) { + return parse(string, position); + } + + private int match(String string, ParsePosition position, boolean last, + String[] tokens) { + int length = string.length(), offset = position.getIndex(), token = -1; + while (offset < length && Character.isWhitespace(string.charAt(offset))) { + offset++; + } + for (int i = tokens.length; --i >= 0;) { + if (string.regionMatches(true, offset, tokens[i], 0, tokens[i] + .length())) { + token = i; + break; + } + } + if (token == -1) { + return -1; + } + offset += tokens[token].length(); + while (offset < length && Character.isWhitespace(string.charAt(offset))) { + offset++; + } + char ch; + if (offset < length + && ((ch = string.charAt(offset)) == '}' || (!last && ch == ','))) { + position.setIndex(offset + 1); + return token; + } + return -1; + } + + private Format parseVariable(String string, ParsePosition position) { + int length = string.length(), offset = position.getIndex(); + char ch; + if (offset >= length + || ((ch = string.charAt(offset++)) != '}' && ch != ',')) { + // text.15=Missing element format + throw new IllegalArgumentException(Messages.getString("text.15")); //$NON-NLS-1$ + } + position.setIndex(offset); + if (ch == '}') { + return null; + } + int type = match(string, position, false, new String[] { "time", //$NON-NLS-1$ + "date", "number", "choice" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (type == -1) { + // text.16=Unknown element format + throw new IllegalArgumentException(Messages.getString("text.16")); //$NON-NLS-1$ + } + StringBuffer buffer = new StringBuffer(); + ch = string.charAt(position.getIndex() - 1); + switch (type) { + case 0: // time + case 1: // date + if (ch == '}') { + return type == 1 ? DateFormat.getDateInstance( + DateFormat.DEFAULT, locale) : DateFormat + .getTimeInstance(DateFormat.DEFAULT, locale); + } + int dateStyle = match(string, position, true, new String[] { + "full", "long", "medium", "short" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + if (dateStyle == -1) { + Format.upToWithQuotes(string, position, buffer, '}', '{'); + return new SimpleDateFormat(buffer.toString(), locale); + } + switch (dateStyle) { + case 0: + dateStyle = DateFormat.FULL; + break; + case 1: + dateStyle = DateFormat.LONG; + break; + case 2: + dateStyle = DateFormat.MEDIUM; + break; + case 3: + dateStyle = DateFormat.SHORT; + break; + } + return type == 1 ? DateFormat + .getDateInstance(dateStyle, locale) : DateFormat + .getTimeInstance(dateStyle, locale); + case 2: // number + if (ch == '}') { + // BEGIN android-changed + return NumberFormat.getInstance(locale); + // END android-changed + } + int numberStyle = match(string, position, true, new String[] { + "currency", "percent", "integer" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (numberStyle == -1) { + Format.upToWithQuotes(string, position, buffer, '}', '{'); + return new DecimalFormat(buffer.toString(), + new DecimalFormatSymbols(locale)); + } + switch (numberStyle) { + case 0: // currency + return NumberFormat.getCurrencyInstance(locale); + case 1: // percent + return NumberFormat.getPercentInstance(locale); + } + return NumberFormat.getIntegerInstance(locale); + } + // choice + try { + Format.upToWithQuotes(string, position, buffer, '}', '{'); + } catch (IllegalArgumentException e) { + // ignored + } + return new ChoiceFormat(buffer.toString()); + } + + /** + * Sets the specified format used by this message format. + * + * @param offset + * the index of the format to change. + * @param format + * the {@code Format} that replaces the old format. + * @since Android 1.0 + */ + public void setFormat(int offset, Format format) { + formats[offset] = format; + } + + /** + * Sets the formats used by this message format. + * + * @param formats + * an array of {@code Format}. + * @since Android 1.0 + */ + public void setFormats(Format[] formats) { + int min = this.formats.length; + if (formats.length < min) { + min = formats.length; + } + for (int i = 0; i < min; i++) { + this.formats[i] = formats[i]; + } + } + + /** + * Sets the locale to use when creating {@code Format} instances. Changing + * the locale may change the behavior of {@code applyPattern}, + * {@code toPattern}, {@code format} and {@code formatToCharacterIterator}. + * + * @param locale + * the new locale. + * @since Android 1.0 + */ + public void setLocale(Locale locale) { + this.locale = locale; + for (int i = 0; i <= maxOffset; i++) { + Format format = formats[i]; + // BEGIN android-removed + //if (format instanceof DecimalFormat) { + // formats[i] = new DecimalFormat(((DecimalFormat) format) + // .toPattern(), new DecimalFormatSymbols(locale)); + //} else if (format instanceof SimpleDateFormat) { + // formats[i] = new SimpleDateFormat(((SimpleDateFormat) format) + // .toPattern(), locale); + //} + // END android-removed + // BEGIN android-added + // java specification undefined for null argument, change into + // a more tolerant implementation + if (format instanceof DecimalFormat) { + try { + formats[i] = new DecimalFormat(((DecimalFormat) format) + .toPattern(), new DecimalFormatSymbols(locale)); + } catch (NullPointerException npe){ + formats[i] = null; + } + } else if (format instanceof SimpleDateFormat) { + try { + formats[i] = new SimpleDateFormat(((SimpleDateFormat) format) + .toPattern(), locale); + } catch (NullPointerException npe) { + formats[i] = null; + } + } + // END android-added + } + } + + private String decodeDecimalFormat(StringBuffer buffer, Format format) { + buffer.append(",number"); //$NON-NLS-1$ + if (format.equals(NumberFormat.getNumberInstance(locale))) { + // Empty block + } else if (format.equals(NumberFormat.getIntegerInstance(locale))) { + buffer.append(",integer"); //$NON-NLS-1$ + } else if (format.equals(NumberFormat.getCurrencyInstance(locale))) { + buffer.append(",currency"); //$NON-NLS-1$ + } else if (format.equals(NumberFormat.getPercentInstance(locale))) { + buffer.append(",percent"); //$NON-NLS-1$ + } else { + buffer.append(','); + return ((DecimalFormat) format).toPattern(); + } + return null; + } + + private String decodeSimpleDateFormat(StringBuffer buffer, Format format) { + if (format.equals(DateFormat + .getTimeInstance(DateFormat.DEFAULT, locale))) { + buffer.append(",time"); //$NON-NLS-1$ + } else if (format.equals(DateFormat.getDateInstance(DateFormat.DEFAULT, + locale))) { + buffer.append(",date"); //$NON-NLS-1$ + } else if (format.equals(DateFormat.getTimeInstance(DateFormat.SHORT, + locale))) { + buffer.append(",time,short"); //$NON-NLS-1$ + } else if (format.equals(DateFormat.getDateInstance(DateFormat.SHORT, + locale))) { + buffer.append(",date,short"); //$NON-NLS-1$ + } else if (format.equals(DateFormat.getTimeInstance(DateFormat.LONG, + locale))) { + buffer.append(",time,long"); //$NON-NLS-1$ + } else if (format.equals(DateFormat.getDateInstance(DateFormat.LONG, + locale))) { + buffer.append(",date,long"); //$NON-NLS-1$ + } else if (format.equals(DateFormat.getTimeInstance(DateFormat.FULL, + locale))) { + buffer.append(",time,full"); //$NON-NLS-1$ + } else if (format.equals(DateFormat.getDateInstance(DateFormat.FULL, + locale))) { + buffer.append(",date,full"); //$NON-NLS-1$ + } else { + buffer.append(",date,"); //$NON-NLS-1$ + return ((SimpleDateFormat) format).toPattern(); + } + return null; + } + + /** + * Returns the pattern of this message format. + * + * @return the pattern. + * @since Android 1.0 + */ + public String toPattern() { + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i <= maxOffset; i++) { + appendQuoted(buffer, strings[i]); + buffer.append('{'); + buffer.append(argumentNumbers[i]); + Format format = formats[i]; + String pattern = null; + if (format instanceof ChoiceFormat) { + buffer.append(",choice,"); //$NON-NLS-1$ + pattern = ((ChoiceFormat) format).toPattern(); + } else if (format instanceof DecimalFormat) { + pattern = decodeDecimalFormat(buffer, format); + } else if (format instanceof SimpleDateFormat) { + pattern = decodeSimpleDateFormat(buffer, format); + } else if (format != null) { + // text.17=Unknown format + throw new IllegalArgumentException(Messages + .getString("text.17")); //$NON-NLS-1$ + } + if (pattern != null) { + boolean quote = false; + int index = 0, length = pattern.length(), count = 0; + while (index < length) { + char ch = pattern.charAt(index++); + if (ch == '\'') { + quote = !quote; + } + if (!quote) { + if (ch == '{') { + count++; + } + if (ch == '}') { + if (count > 0) { + count--; + } else { + buffer.append("'}"); //$NON-NLS-1$ + ch = '\''; + } + } + } + buffer.append(ch); + } + } + buffer.append('}'); + } + if (maxOffset + 1 < strings.length) { + appendQuoted(buffer, strings[maxOffset + 1]); + } + return buffer.toString(); + } + + private void appendQuoted(StringBuffer buffer, String string) { + int length = string.length(); + for (int i = 0; i < length; i++) { + char ch = string.charAt(i); + if (ch == '{' || ch == '}') { + buffer.append('\''); + buffer.append(ch); + buffer.append('\''); + } else { + buffer.append(ch); + } + } + } + + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("argumentNumbers", int[].class), //$NON-NLS-1$ + new ObjectStreamField("formats", Format[].class), //$NON-NLS-1$ + new ObjectStreamField("locale", Locale.class), //$NON-NLS-1$ + new ObjectStreamField("maxOffset", Integer.TYPE), //$NON-NLS-1$ + new ObjectStreamField("offsets", int[].class), //$NON-NLS-1$ + new ObjectStreamField("pattern", String.class), }; //$NON-NLS-1$ + + private void writeObject(ObjectOutputStream stream) throws IOException { + ObjectOutputStream.PutField fields = stream.putFields(); + fields.put("argumentNumbers", argumentNumbers); //$NON-NLS-1$ + Format[] compatibleFormats = formats; + fields.put("formats", compatibleFormats); //$NON-NLS-1$ + fields.put("locale", locale); //$NON-NLS-1$ + fields.put("maxOffset", maxOffset); //$NON-NLS-1$ + int offset = 0; + int offsetsLength = maxOffset + 1; + int[] offsets = new int[offsetsLength]; + StringBuffer pattern = new StringBuffer(); + for (int i = 0; i <= maxOffset; i++) { + offset += strings[i].length(); + offsets[i] = offset; + pattern.append(strings[i]); + } + if (maxOffset + 1 < strings.length) { + pattern.append(strings[maxOffset + 1]); + } + fields.put("offsets", offsets); //$NON-NLS-1$ + fields.put("pattern", pattern.toString()); //$NON-NLS-1$ + stream.writeFields(); + } + + private void readObject(ObjectInputStream stream) throws IOException, + ClassNotFoundException { + ObjectInputStream.GetField fields = stream.readFields(); + argumentNumbers = (int[]) fields.get("argumentNumbers", null); //$NON-NLS-1$ + formats = (Format[]) fields.get("formats", null); //$NON-NLS-1$ + locale = (Locale) fields.get("locale", null); //$NON-NLS-1$ + maxOffset = fields.get("maxOffset", 0); //$NON-NLS-1$ + int[] offsets = (int[]) fields.get("offsets", null); //$NON-NLS-1$ + String pattern = (String) fields.get("pattern", null); //$NON-NLS-1$ + int length; + if (maxOffset < 0) { + length = pattern.length() > 0 ? 1 : 0; + } else { + length = maxOffset + + (offsets[maxOffset] == pattern.length() ? 1 : 2); + } + strings = new String[length]; + int last = 0; + for (int i = 0; i <= maxOffset; i++) { + strings[i] = pattern.substring(last, offsets[i]); + last = offsets[i]; + } + if (maxOffset + 1 < strings.length) { + strings[strings.length - 1] = pattern.substring(last, pattern + .length()); + } + } + + /** + * The instances of this inner class are used as attribute keys in + * {@code AttributedCharacterIterator} that the + * {@link MessageFormat#formatToCharacterIterator(Object)} method returns. + * <p> + * There is no public constructor in this class, the only instances are the + * constants defined here. + * </p> + * + * @since Android 1.0 + */ + public static class Field extends Format.Field { + + private static final long serialVersionUID = 7899943957617360810L; + + // BEGIN android-removed + // public static final Field ARGUMENT = new Field("message argument field"); //$NON-NLS-1$ + // END android-removed + + /** + * This constant stands for the message argument. + * + * @since Android 1.0 + */ + public static final Field ARGUMENT = new Field("message argument field"); //$NON-NLS-1$ + + /** + * Constructs a new instance of {@code MessageFormat.Field} with the + * given field name. + * + * @param fieldName + * the field name. + * @since Android 1.0 + */ + protected Field(String fieldName) { + super(fieldName); + } + + /** + * Resolves instances that are deserialized to the constant + * {@code MessageFormat.Field} values. + * + * @return the resolved field object. + * @throws InvalidObjectException + * if an error occurs while resolving the field object. + */ + @Override + protected Object readResolve() throws InvalidObjectException { + String name = this.getName(); + if (name == null) { + // text.18=Not a valid {0}, subclass should override + // readResolve() + throw new InvalidObjectException(Messages.getString( + "text.18", "MessageFormat.Field")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + if (name.equals(ARGUMENT.getName())) { + return ARGUMENT; + } + // text.18=Not a valid {0}, subclass should override readResolve() + throw new InvalidObjectException(Messages.getString( + "text.18", "MessageFormat.Field")); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + +} diff --git a/text/src/main/java/java/text/NumberFormat.java b/text/src/main/java/java/text/NumberFormat.java new file mode 100644 index 0000000..7510240 --- /dev/null +++ b/text/src/main/java/java/text/NumberFormat.java @@ -0,0 +1,973 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** +******************************************************************************* +* Copyright (C) 1996-2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc and some of the method descriptions are copied from ICU4J +// source files. Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// END android-note + +package java.text; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.util.Currency; +import java.util.Locale; +import java.util.ResourceBundle; + +import org.apache.harmony.text.internal.nls.Messages; + +/** + * The abstract base class for all number formats. This class provides the + * interface for formatting and parsing numbers. {@code NumberFormat} also + * provides methods for determining which locales have number formats, and what + * their names are. + * <p> + * {@code NumberFormat} helps you to format and parse numbers for any locale. + * Your code can be completely independent of the locale conventions for decimal + * points, thousands-separators, or even the particular decimal digits used, or + * whether the number format is even decimal. + * </p> + * <p> + * To format a number for the current locale, use one of the factory class + * methods: + * </p> + * <blockquote> + * + * <pre> + * myString = NumberFormat.getInstance().format(myNumber); + * </pre> + * + * </blockquote> + * <p> + * If you are formatting multiple numbers, it is more efficient to get the + * format and use it multiple times so that the system doesn't have to fetch the + * information about the local language and country conventions multiple times. + * </p> + * <blockquote> + * + * <pre> + * NumberFormat nf = NumberFormat.getInstance(); + * for (int i = 0; i < a.length; ++i) { + * output.println(nf.format(myNumber[i]) + "; "); + * } + * </pre> + * + * </blockquote> + * <p> + * To format a number for a different locale, specify it in the call to + * {@code getInstance}. + * </p> + * <blockquote> + * + * <pre> + * NumberFormat nf = NumberFormat.getInstance(Locale.FRENCH); + * </pre> + * + * </blockquote> + * <p> + * You can also use a {@code NumberFormat} to parse numbers: + * </p> + * <blockquote> + * + * <pre> + * myNumber = nf.parse(myString); + * </pre> + * + * </blockquote> + * <p> + * Use {@code getInstance} or {@code getNumberInstance} to get the normal number + * format. Use {@code getIntegerInstance} to get an integer number format. Use + * {@code getCurrencyInstance} to get the currency number format and use + * {@code getPercentInstance} to get a format for displaying percentages. With + * this format, a fraction like 0.53 is displayed as 53%. + * </p> + * <p> + * You can also control the display of numbers with methods such as + * {@code setMinimumFractionDigits}. If you want even more control over the + * format or parsing, or want to give your users more control, you can try + * casting the {@code NumberFormat} you get from the factory methods to a + * {@code DecimalFormat}. This will work for the vast majority of locales; just + * remember to put it in a {@code try} block in case you encounter an unusual + * one. + * </p> + * <p> + * {@code NumberFormat} is designed such that some controls work for formatting + * and others work for parsing. For example, {@code setParseIntegerOnly} only + * affects parsing: If set to {@code true}, "3456.78" is parsed as 3456 (and + * leaves the parse position just after '6'); if set to {@code false}, + * "3456.78" is parsed as 3456.78 (and leaves the parse position just after + * '8'). This is independent of formatting. + * </p> + * <p> + * You can also use forms of the {@code parse} and {@code format} methods with + * {@code ParsePosition} and {@code FieldPosition} to allow you to: + * <ul> + * <li>progressively parse through pieces of a string;</li> + * <li>align the decimal point and other areas.</li> + * </ul> + * For example, you can align numbers in two ways: + * <ol> + * <li> If you are using a monospaced font with spacing for alignment, you can + * pass the {@code FieldPosition} in your format call, with {@code field} = + * {@code INTEGER_FIELD}. On output, {@code getEndIndex} will be set to the + * offset between the last character of the integer and the decimal. Add + * (desiredSpaceCount - getEndIndex) spaces to the front of the string.</li> + * <li> If you are using proportional fonts, instead of padding with spaces, + * measure the width of the string in pixels from the start to + * {@code getEndIndex}. Then move the pen by (desiredPixelWidth - + * widthToAlignmentPoint) before drawing the text. This also works where there + * is no decimal but possibly additional characters before or after the number, + * for example with parentheses in negative numbers: "(12)" for -12.</li> + * </ol> + * <h4>Synchronization</h4> + * <p> + * Number formats are generally not synchronized. It is recommended to create + * separate format instances for each thread. If multiple threads access a + * format concurrently, it must be synchronized externally. + * <p> + * <h4>DecimalFormat</h4> + * <p> + * {@code DecimalFormat} is the concrete implementation of {@code NumberFormat}, + * and the {@code NumberFormat} API is essentially an abstraction of + * {@code DecimalFormat's} API. Refer to {@code DecimalFormat} for more + * information about this API. + * </p> + * + * @see DecimalFormat + * @see java.text.ChoiceFormat + * @since Android 1.0 + */ +public abstract class NumberFormat extends Format { + + private static final long serialVersionUID = -2308460125733713944L; + + /** + * Field constant identifying the integer part of a number. + * + * @since Android 1.0 + */ + public static final int INTEGER_FIELD = 0; + + /** + * Field constant identifying the fractional part of a number. + * + * @since Android 1.0 + */ + public static final int FRACTION_FIELD = 1; + + private boolean groupingUsed = true, parseIntegerOnly = false; + + private int maximumIntegerDigits = 40, minimumIntegerDigits = 1, + maximumFractionDigits = 3, minimumFractionDigits = 0; + + /** + * Constructs a new instance of {@code NumberFormat}. + * + * @since Android 1.0 + */ + public NumberFormat() { + } + + /** + * Returns a new {@code NumberFormat} with the same properties as this + * {@code NumberFormat}. + * + * @return a shallow copy of this {@code NumberFormat}. + * @see java.lang.Cloneable + * @since Android 1.0 + */ + @Override + public Object clone() { + return super.clone(); + } + + /** + * Compares the specified object to this number format and indicates if + * they are equal. In order to be equal, {@code object} must be an instance + * of {@code NumberFormat} with the same pattern and properties. + * + * @param object + * the object to compare with this object. + * @return {@code true} if the specified object is equal to this number + * format; {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof NumberFormat)) { + return false; + } + NumberFormat obj = (NumberFormat) object; + return groupingUsed == obj.groupingUsed + && parseIntegerOnly == obj.parseIntegerOnly + && maximumFractionDigits == obj.maximumFractionDigits + && maximumIntegerDigits == obj.maximumIntegerDigits + && minimumFractionDigits == obj.minimumFractionDigits + && minimumIntegerDigits == obj.minimumIntegerDigits; + } + + /** + * Formats the specified double using the rules of this number format. + * + * @param value + * the double to format. + * @return the formatted string. + * @since Android 1.0 + */ + public final String format(double value) { + return format(value, new StringBuffer(), new FieldPosition(0)) + .toString(); + } + + /** + * Formats the specified double value as a string using the pattern of this + * number format and appends the string to the specified string buffer. + * <p> + * If the {@code field} member of {@code position} contains a value + * specifying a format field, then its {@code beginIndex} and + * {@code endIndex} members will be updated with the position of the first + * occurrence of this field in the formatted text. + * </p> + * + * @param value + * the double to format. + * @param buffer + * the target string buffer to append the formatted double value + * to. + * @param field + * on input: an optional alignment field; on output: the offsets + * of the alignment field in the formatted text. + * @return the string buffer. + * @since Android 1.0 + */ + public abstract StringBuffer format(double value, StringBuffer buffer, + FieldPosition field); + + /** + * Formats the specified long using the rules of this number format. + * + * @param value + * the long to format. + * @return the formatted string. + * @since Android 1.0 + */ + public final String format(long value) { + return format(value, new StringBuffer(), new FieldPosition(0)) + .toString(); + } + + /** + * Formats the specified long value as a string using the pattern of this + * number format and appends the string to the specified string buffer. + * <p> + * If the {@code field} member of {@code position} contains a value + * specifying a format field, then its {@code beginIndex} and + * {@code endIndex} members will be updated with the position of the first + * occurrence of this field in the formatted text. + * </p> + * + * @param value + * the long to format. + * @param buffer + * the target string buffer to append the formatted long value + * to. + * @param field + * on input: an optional alignment field; on output: the offsets + * of the alignment field in the formatted text. + * @return the string buffer. + * @since Android 1.0 + */ + public abstract StringBuffer format(long value, StringBuffer buffer, + FieldPosition field); + + /** + * Formats the specified object as a string using the pattern of this number + * format and appends the string to the specified string buffer. + * <p> + * If the {@code field} member of {@code field} contains a value specifying + * a format field, then its {@code beginIndex} and {@code endIndex} members + * will be updated with the position of the first occurrence of this field + * in the formatted text. + * </p> + * + * @param object + * the object to format, must be a {@code Number}. + * @param buffer + * the target string buffer to append the formatted number to. + * @param field + * on input: an optional alignment field; on output: the offsets + * of the alignment field in the formatted text. + * @return the string buffer. + * @throws IllegalArgumentException + * if {@code object} is not an instance of {@code Number}. + * @since Android 1.0 + */ + @Override + public StringBuffer format(Object object, StringBuffer buffer, + FieldPosition field) { + if (object instanceof Number) { + double dv = ((Number) object).doubleValue(); + long lv = ((Number) object).longValue(); + if (dv == lv) { + return format(lv, buffer, field); + } + return format(dv, buffer, field); + } + throw new IllegalArgumentException(); + } + + /** + * Gets the list of installed locales which support {@code NumberFormat}. + * + * @return an array of locales. + * @since Android 1.0 + */ + public static Locale[] getAvailableLocales() { + return Locale.getAvailableLocales(); + } + + /** + * Returns the currency used by this number format. + * <p> + * This implementation throws {@code UnsupportedOperationException}, + * concrete subclasses should override this method if they support currency + * formatting. + * <p> + * + * @return the currency that was set in getInstance() or in setCurrency(), + * or {@code null}. + * @throws UnsupportedOperationException + * @since Android 1.0 + */ + public Currency getCurrency() { + throw new UnsupportedOperationException(); + } + + /** + * Returns a {@code NumberFormat} for formatting and parsing currency values + * for the default locale. + * + * @return a {@code NumberFormat} for handling currency values. + * @since Android 1.0 + */ + public final static NumberFormat getCurrencyInstance() { + return getCurrencyInstance(Locale.getDefault()); + } + + /** + * Returns a {@code NumberFormat} for formatting and parsing currency values + * for the specified locale. + * + * @param locale + * the locale to use. + * @return a {@code NumberFormat} for handling currency values. + * @since Android 1.0 + */ + public static NumberFormat getCurrencyInstance(Locale locale) { + return getInstance(locale, "Currency"); //$NON-NLS-1$ + } + + /** + * Returns a {@code NumberFormat} for formatting and parsing integers for the + * default locale. + * + * @return a {@code NumberFormat} for handling integers. + * @since Android 1.0 + */ + public final static NumberFormat getIntegerInstance() { + return getIntegerInstance(Locale.getDefault()); + } + + /** + * Returns a {@code NumberFormat} for formatting and parsing integers for + * the specified locale. + * + * @param locale + * the locale to use. + * @return a {@code NumberFormat} for handling integers. + * @since Android 1.0 + */ + public static NumberFormat getIntegerInstance(Locale locale) { + NumberFormat format = getInstance(locale, "Integer"); //$NON-NLS-1$ + format.setParseIntegerOnly(true); + return format; + } + + /** + * Returns a {@code NumberFormat} for formatting and parsing numbers for the + * default locale. + * + * @return a {@code NumberFormat} for handling {@code Number} objects. + * @since Android 1.0 + */ + public final static NumberFormat getInstance() { + return getNumberInstance(); + } + + /** + * Returns a {@code NumberFormat} for formatting and parsing numbers for the + * specified locale. + * + * @param locale + * the locale to use. + * @return a {@code NumberFormat} for handling {@code Number} objects. + * @since Android 1.0 + */ + public static NumberFormat getInstance(Locale locale) { + return getNumberInstance(locale); + } + + static NumberFormat getInstance(Locale locale, String type) { + return new DecimalFormat(getPattern(locale, type), + new DecimalFormatSymbols(locale)); + } + + /** + * Returns the maximum number of fraction digits that are printed when + * formatting. If the maximum is less than the number of fraction digits, + * the least significant digits are truncated. + * + * @return the maximum number of fraction digits. + * @since Android 1.0 + */ + public int getMaximumFractionDigits() { + return maximumFractionDigits; + } + + /** + * Returns the maximum number of integer digits that are printed when + * formatting. If the maximum is less than the number of integer digits, the + * most significant digits are truncated. + * + * @return the maximum number of integer digits. + * @since Android 1.0 + */ + public int getMaximumIntegerDigits() { + return maximumIntegerDigits; + } + + /** + * Returns the minimum number of fraction digits that are printed when + * formatting. + * + * @return the minimum number of fraction digits. + * @since Android 1.0 + */ + public int getMinimumFractionDigits() { + return minimumFractionDigits; + } + + /** + * Returns the minimum number of integer digits that are printed when + * formatting. + * + * @return the minimum number of integer digits. + * @since Android 1.0 + */ + public int getMinimumIntegerDigits() { + return minimumIntegerDigits; + } + + /** + * Returns a {@code NumberFormat} for formatting and parsing numbers for the + * default locale. + * + * @return a {@code NumberFormat} for handling {@code Number} objects. + * @since Android 1.0 + */ + public final static NumberFormat getNumberInstance() { + return getNumberInstance(Locale.getDefault()); + } + + /** + * Returns a {@code NumberFormat} for formatting and parsing numbers for the + * specified locale. + * + * @param locale + * the locale to use. + * @return a {@code NumberFormat} for handling {@code Number} objects. + * @since Android 1.0 + */ + public static NumberFormat getNumberInstance(Locale locale) { + return getInstance(locale, "Number"); //$NON-NLS-1$ + } + + static String getPattern(Locale locale, String type) { + ResourceBundle bundle = getBundle(locale); + return bundle.getString(type); + } + + /** + * Returns a {@code NumberFormat} for formatting and parsing percentage + * values for the default locale. + * + * @return a {@code NumberFormat} for handling percentage values. + * @since Android 1.0 + */ + public final static NumberFormat getPercentInstance() { + return getPercentInstance(Locale.getDefault()); + } + + /** + * Returns a {@code NumberFormat} for formatting and parsing percentage + * values for the specified locale. + * + * @param locale + * the locale to use. + * @return a {@code NumberFormat} for handling percentage values. + * @since Android 1.0 + */ + public static NumberFormat getPercentInstance(Locale locale) { + return getInstance(locale, "Percent"); //$NON-NLS-1$ + } + + @Override + public int hashCode() { + return (groupingUsed ? 1231 : 1237) + (parseIntegerOnly ? 1231 : 1237) + + maximumFractionDigits + maximumIntegerDigits + + minimumFractionDigits + minimumIntegerDigits; + } + + /** + * Indicates whether this number format formats and parses numbers using a + * grouping separator. + * + * @return {@code true} if a grouping separator is used; {@code false} + * otherwise. + * @since Android 1.0 + */ + public boolean isGroupingUsed() { + return groupingUsed; + } + + /** + * Indicates whether this number format only parses integer numbers. Parsing + * stops if a decimal separator is encountered. + * + * @return {@code true} if this number format only parses integers, + * {@code false} if if parsese integers as well as fractions. + * @since Android 1.0 + */ + public boolean isParseIntegerOnly() { + return parseIntegerOnly; + } + + /** + * Parses a {@code Number} from the specified string using the rules of this + * number format. + * + * @param string + * the string to parse. + * @return the {@code Number} resulting from the parsing. + * @exception ParseException + * if an error occurs during parsing. + * @since Android 1.0 + */ + public Number parse(String string) throws ParseException { + ParsePosition pos = new ParsePosition(0); + Number number = parse(string, pos); + if (pos.getErrorIndex() != -1 || pos.getIndex() == 0) { + throw new ParseException(null, pos.getErrorIndex()); + } + return number; + } + + /** + * Parses a {@code Number} from the specified string starting at the index + * specified by {@code position}. If the string is successfully parsed then + * the index of the {@code ParsePosition} is updated to the index following + * the parsed text. On error, the index is unchanged and the error index of + * {@code ParsePosition} is set to the index where the error occurred. + * + * @param string + * the string to parse. + * @param position + * input/output parameter, specifies the start index in + * {@code string} from where to start parsing. If parsing is + * successful, it is updated with the index following the parsed + * text; on error, the index is unchanged and the error index is + * set to the index where the error occurred. + * @return the {@code Number} resulting from the parse or {@code null} if + * there is an error. + * @since Android 1.0 + */ + public abstract Number parse(String string, ParsePosition position); + + @Override + public final Object parseObject(String string, ParsePosition position) { + if (position == null) { + // text.1A=position is null + throw new NullPointerException(Messages.getString("text.1A")); //$NON-NLS-1$ + } + + try { + return parse(string, position); + } catch (Exception e) { + return null; + } + } + + /** + * Sets the currency used by this number format when formatting currency + * values. The min and max fraction digits remain the same. + * <p> + * This implementation throws {@code UnsupportedOperationException}, + * concrete subclasses should override this method if they support currency + * formatting. + * </p> + * + * @param currency + * the new currency. + * @throws UnsupportedOperationException + * @since Android 1.0 + */ + public void setCurrency(Currency currency) { + throw new UnsupportedOperationException(); + } + + /** + * Sets whether this number format formats and parses numbers using a + * grouping separator. + * + * @param value + * {@code true} if a grouping separator is used; {@code false} + * otherwise. + * @since Android 1.0 + */ + public void setGroupingUsed(boolean value) { + groupingUsed = value; + } + + /** + * Sets the maximum number of fraction digits that are printed when + * formatting. If the maximum is less than the number of fraction digits, + * the least significant digits are truncated. + * + * @param value + * the maximum number of fraction digits. + * @since Android 1.0 + */ + public void setMaximumFractionDigits(int value) { + maximumFractionDigits = value < 0 ? 0 : value; + if (maximumFractionDigits < minimumFractionDigits) { + minimumFractionDigits = maximumFractionDigits; + } + } + + /** + * Sets the new maximum count of integer digits that are printed when + * formatting. If the maximum is less than the number of integer digits, the + * most significant digits are truncated. + * + * @param value + * the new maximum number of integer numerals for display. + * @since Android 1.0 + */ + public void setMaximumIntegerDigits(int value) { + maximumIntegerDigits = value < 0 ? 0 : value; + if (maximumIntegerDigits < minimumIntegerDigits) { + minimumIntegerDigits = maximumIntegerDigits; + } + } + + /** + * Sets the minimum number of fraction digits that are printed when + * formatting. + * + * @param value + * the minimum number of fraction digits. + * @since Android 1.0 + */ + public void setMinimumFractionDigits(int value) { + minimumFractionDigits = value < 0 ? 0 : value; + if (maximumFractionDigits < minimumFractionDigits) { + maximumFractionDigits = minimumFractionDigits; + } + } + + /** + * Sets the minimum number of integer digits that are printed when + * formatting. + * + * @param value + * the minimum number of integer digits. + * @since Android 1.0 + */ + public void setMinimumIntegerDigits(int value) { + minimumIntegerDigits = value < 0 ? 0 : value; + if (maximumIntegerDigits < minimumIntegerDigits) { + maximumIntegerDigits = minimumIntegerDigits; + } + } + + /** + * Specifies if this number format should parse numbers only as integers or + * else as any kind of number. If this method is called with a {@code true} + * value then subsequent parsing attempts will stop if a decimal separator + * is encountered. + * + * @param value + * {@code true} to only parse integers, {@code false} to parse + * integers as well as fractions. + * @since Android 1.0 + */ + public void setParseIntegerOnly(boolean value) { + parseIntegerOnly = value; + } + + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("groupingUsed", Boolean.TYPE), //$NON-NLS-1$ + new ObjectStreamField("maxFractionDigits", Byte.TYPE), //$NON-NLS-1$ + new ObjectStreamField("maximumFractionDigits", Integer.TYPE), //$NON-NLS-1$ + new ObjectStreamField("maximumIntegerDigits", Integer.TYPE), //$NON-NLS-1$ + new ObjectStreamField("maxIntegerDigits", Byte.TYPE), //$NON-NLS-1$ + new ObjectStreamField("minFractionDigits", Byte.TYPE), //$NON-NLS-1$ + new ObjectStreamField("minimumFractionDigits", Integer.TYPE), //$NON-NLS-1$ + new ObjectStreamField("minimumIntegerDigits", Integer.TYPE), //$NON-NLS-1$ + new ObjectStreamField("minIntegerDigits", Byte.TYPE), //$NON-NLS-1$ + new ObjectStreamField("parseIntegerOnly", Boolean.TYPE), //$NON-NLS-1$ + new ObjectStreamField("serialVersionOnStream", Integer.TYPE), }; //$NON-NLS-1$ + + private void writeObject(ObjectOutputStream stream) throws IOException { + ObjectOutputStream.PutField fields = stream.putFields(); + fields.put("groupingUsed", groupingUsed); //$NON-NLS-1$ + fields + .put( + "maxFractionDigits", //$NON-NLS-1$ + maximumFractionDigits < Byte.MAX_VALUE ? (byte) maximumFractionDigits + : Byte.MAX_VALUE); + fields.put("maximumFractionDigits", maximumFractionDigits); //$NON-NLS-1$ + fields.put("maximumIntegerDigits", maximumIntegerDigits); //$NON-NLS-1$ + fields + .put( + "maxIntegerDigits", //$NON-NLS-1$ + maximumIntegerDigits < Byte.MAX_VALUE ? (byte) maximumIntegerDigits + : Byte.MAX_VALUE); + fields + .put( + "minFractionDigits", //$NON-NLS-1$ + minimumFractionDigits < Byte.MAX_VALUE ? (byte) minimumFractionDigits + : Byte.MAX_VALUE); + fields.put("minimumFractionDigits", minimumFractionDigits); //$NON-NLS-1$ + fields.put("minimumIntegerDigits", minimumIntegerDigits); //$NON-NLS-1$ + fields + .put( + "minIntegerDigits", //$NON-NLS-1$ + minimumIntegerDigits < Byte.MAX_VALUE ? (byte) minimumIntegerDigits + : Byte.MAX_VALUE); + fields.put("parseIntegerOnly", parseIntegerOnly); //$NON-NLS-1$ + fields.put("serialVersionOnStream", 1); //$NON-NLS-1$ + stream.writeFields(); + } + + private void readObject(ObjectInputStream stream) throws IOException, + ClassNotFoundException { + ObjectInputStream.GetField fields = stream.readFields(); + groupingUsed = fields.get("groupingUsed", true); //$NON-NLS-1$ + parseIntegerOnly = fields.get("parseIntegerOnly", false); //$NON-NLS-1$ + if (fields.get("serialVersionOnStream", 0) == 0) { //$NON-NLS-1$ + maximumFractionDigits = fields.get("maxFractionDigits", (byte) 3); //$NON-NLS-1$ + maximumIntegerDigits = fields.get("maxIntegerDigits", (byte) 40); //$NON-NLS-1$ + minimumFractionDigits = fields.get("minFractionDigits", (byte) 0); //$NON-NLS-1$ + minimumIntegerDigits = fields.get("minIntegerDigits", (byte) 1); //$NON-NLS-1$ + } else { + maximumFractionDigits = fields.get("maximumFractionDigits", 3); //$NON-NLS-1$ + maximumIntegerDigits = fields.get("maximumIntegerDigits", 40); //$NON-NLS-1$ + minimumFractionDigits = fields.get("minimumFractionDigits", 0); //$NON-NLS-1$ + minimumIntegerDigits = fields.get("minimumIntegerDigits", 1); //$NON-NLS-1$ + } + if (minimumIntegerDigits > maximumIntegerDigits + || minimumFractionDigits > maximumFractionDigits) { + // text.00=min digits greater than max digits + throw new InvalidObjectException(Messages.getString("text.00")); //$NON-NLS-1$ + } + if (minimumIntegerDigits < 0 || maximumIntegerDigits < 0 + || minimumFractionDigits < 0 || maximumFractionDigits < 0) { + // text.01=min or max digits negative + throw new InvalidObjectException(Messages.getString("text.01")); //$NON-NLS-1$ + } + } + + /** + * The instances of this inner class are used as attribute keys and values + * in {@code AttributedCharacterIterator} that the + * {@link NumberFormat#formatToCharacterIterator(Object)} method returns. + * <p> + * There is no public constructor in this class, the only instances are the + * constants defined here. + * <p> + * + * @since Android 1.0 + */ + public static class Field extends Format.Field { + + private static final long serialVersionUID = 7494728892700160890L; + + /** + * This constant stands for the number sign. + * + * @since Android 1.0 + */ + public static final Field SIGN = new Field("sign"); //$NON-NLS-1$ + + /** + * This constant stands for the integer part of the number. + * + * @since Android 1.0 + */ + public static final Field INTEGER = new Field("integer"); //$NON-NLS-1$ + + /** + * This constant stands for the fraction part of the number. + * + * @since Android 1.0 + */ + public static final Field FRACTION = new Field("fraction"); //$NON-NLS-1$ + + /** + * This constant stands for the exponent part of the number. + * + * @since Android 1.0 + */ + public static final Field EXPONENT = new Field("exponent"); //$NON-NLS-1$ + + /** + * This constant stands for the exponent sign symbol. + * + * @since Android 1.0 + */ + public static final Field EXPONENT_SIGN = new Field("exponent sign"); //$NON-NLS-1$ + + /** + * This constant stands for the exponent symbol. + * + * @since Android 1.0 + */ + public static final Field EXPONENT_SYMBOL = new Field("exponent symbol"); //$NON-NLS-1$ + + /** + * This constant stands for the decimal separator. + * + * @since Android 1.0 + */ + public static final Field DECIMAL_SEPARATOR = new Field( + "decimal separator"); //$NON-NLS-1$ + + /** + * This constant stands for the grouping separator. + * + * @since Android 1.0 + */ + public static final Field GROUPING_SEPARATOR = new Field( + "grouping separator"); //$NON-NLS-1$ + + /** + * This constant stands for the percent symbol. + * + * @since Android 1.0 + */ + public static final Field PERCENT = new Field("percent"); //$NON-NLS-1$ + + /** + * This constant stands for the permille symbol. + * + * @since Android 1.0 + */ + public static final Field PERMILLE = new Field("per mille"); //$NON-NLS-1$ + + /** + * This constant stands for the currency symbol. + * + * @since Android 1.0 + */ + public static final Field CURRENCY = new Field("currency"); //$NON-NLS-1$ + + /** + * Constructs a new instance of {@code NumberFormat.Field} with the + * given field name. + * + * @param fieldName + * the field name. + * @since Android 1.0 + */ + protected Field(String fieldName) { + super(fieldName); + } + + /** + * Resolves instances that are deserialized to the constant + * {@code NumberFormat.Field} values. + * + * @return the resolved field object. + * @throws InvalidObjectException + * if an error occurs while resolving the field object. + */ + @Override + protected Object readResolve() throws InvalidObjectException { + if (this.equals(INTEGER)) { + return INTEGER; + } + if (this.equals(FRACTION)) { + return FRACTION; + } + if (this.equals(EXPONENT)) { + return EXPONENT; + } + if (this.equals(EXPONENT_SIGN)) { + return EXPONENT_SIGN; + } + if (this.equals(EXPONENT_SYMBOL)) { + return EXPONENT_SYMBOL; + } + if (this.equals(CURRENCY)) { + return CURRENCY; + } + if (this.equals(DECIMAL_SEPARATOR)) { + return DECIMAL_SEPARATOR; + } + if (this.equals(GROUPING_SEPARATOR)) { + return GROUPING_SEPARATOR; + } + if (this.equals(PERCENT)) { + return PERCENT; + } + if (this.equals(PERMILLE)) { + return PERMILLE; + } + if (this.equals(SIGN)) { + return SIGN; + } + // text.02=Unknown attribute + throw new InvalidObjectException(Messages.getString("text.02")); //$NON-NLS-1$ + } + } + +} diff --git a/text/src/main/java/java/text/ParseException.java b/text/src/main/java/java/text/ParseException.java new file mode 100644 index 0000000..5218462 --- /dev/null +++ b/text/src/main/java/java/text/ParseException.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.text; + +/** + * Thrown when the string being parsed is not in the correct form. + * + * @since Android 1.0 + */ +public class ParseException extends Exception { + + private static final long serialVersionUID = 2703218443322787634L; + + private int errorOffset; + + /** + * Constructs a new instance of this class with its stack trace, detail + * message and the location of the error filled in. + * + * @param detailMessage + * the detail message for this exception. + * @param location + * the index at which the parse exception occurred. + * @since Android 1.0 + */ + public ParseException(String detailMessage, int location) { + super(detailMessage); + errorOffset = location; + } + + /** + * Returns the index at which this parse exception occurred. + * + * @return the location of this exception in the parsed string. + * @since Android 1.0 + */ + public int getErrorOffset() { + return errorOffset; + } +} diff --git a/text/src/main/java/java/text/ParsePosition.java b/text/src/main/java/java/text/ParsePosition.java new file mode 100644 index 0000000..c3d1001 --- /dev/null +++ b/text/src/main/java/java/text/ParsePosition.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.text; + +/** + * Tracks the current position in a parsed string. In case of an error the error + * index can be set to the position where the error occurred without having to + * change the parse position. + * + * @since Android 1.0 + */ +public class ParsePosition { + + private int currentPosition, errorIndex = -1; + + /** + * Constructs a new {@code ParsePosition} with the specified index. + * + * @param index + * the index to begin parsing. + * @since Android 1.0 + */ + public ParsePosition(int index) { + currentPosition = index; + } + + /** + * Compares the specified object to this {@code ParsePosition} and indicates + * if they are equal. In order to be equal, {@code object} must be an + * instance of {@code ParsePosition} and it must have the same index and + * error index. + * + * @param object + * the object to compare with this object. + * @return {@code true} if the specified object is equal to this + * {@code ParsePosition}; {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof ParsePosition)) { + return false; + } + ParsePosition pos = (ParsePosition) object; + return currentPosition == pos.currentPosition + && errorIndex == pos.errorIndex; + } + + /** + * Returns the index at which the parse could not continue. + * + * @return the index of the parse error or -1 if there is no error. + * @since Android 1.0 + */ + public int getErrorIndex() { + return errorIndex; + } + + /** + * Returns the current parse position. + * + * @return the current position. + * @since Android 1.0 + */ + public int getIndex() { + return currentPosition; + } + + @Override + public int hashCode() { + return currentPosition + errorIndex; + } + + /** + * Sets the index at which the parse could not continue. + * + * @param index + * the index of the parse error. + * @since Android 1.0 + */ + public void setErrorIndex(int index) { + errorIndex = index; + } + + /** + * Sets the current parse position. + * + * @param index + * the current parse position. + * @since Android 1.0 + */ + public void setIndex(int index) { + currentPosition = index; + } + + /** + * Returns the string representation of this parse position. + * + * @return the string representation of this parse position. + * @since Android 1.0 + */ + @Override + public String toString() { + return getClass().getName() + "[index=" + currentPosition //$NON-NLS-1$ + + ", errorIndex=" + errorIndex + "]"; //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/text/src/main/java/java/text/RuleBasedBreakIterator.java b/text/src/main/java/java/text/RuleBasedBreakIterator.java new file mode 100644 index 0000000..2ca6f9b --- /dev/null +++ b/text/src/main/java/java/text/RuleBasedBreakIterator.java @@ -0,0 +1,209 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// BEGIN android-note +// The icu implementation used was changed from icu4j to icu4jni. +// END android-note + +package java.text; + +/* + * Default implementation of BreakIterator, wrap + * com.ibm.icu4jni.text.RuleBasedBreakIterator + * + */ +class RuleBasedBreakIterator extends BreakIterator { + + /* + * Wrapping construction + */ + RuleBasedBreakIterator(com.ibm.icu4jni.text.BreakIterator iterator) { + super(iterator); + } + + /* + * (non-Javadoc) + * + * @see java.text.BreakIterator#current() + */ + @Override + public int current() { + return wrapped.current(); + } + + /* + * (non-Javadoc) + * + * @see java.text.BreakIterator#first() + */ + @Override + public int first() { + return wrapped.first(); + } + + /* + * (non-Javadoc) + * + * @see java.text.BreakIterator#following(int) + */ + @Override + public int following(int offset) { + validateOffset(offset); + return wrapped.following(offset); + } + + /* + * check the offset, throw exception if it is invalid + */ + private void validateOffset(int offset) { + CharacterIterator it = wrapped.getText(); + if (offset < it.getBeginIndex() || offset >= it.getEndIndex()) { + throw new IllegalArgumentException(); + } + } + + /* + * (non-Javadoc) + * + * @see java.text.BreakIterator#getText() + */ + @Override + public CharacterIterator getText() { + return wrapped.getText(); + } + + /* + * (non-Javadoc) + * + * @see java.text.BreakIterator#last() + */ + @Override + public int last() { + return wrapped.last(); + } + + /* + * (non-Javadoc) + * + * @see java.text.BreakIterator#next() + */ + @Override + public int next() { + return wrapped.next(); + } + + /* + * (non-Javadoc) + * + * @see java.text.BreakIterator#next(int) + */ + @Override + public int next(int n) { + return wrapped.next(n); + } + + /* + * (non-Javadoc) + * + * @see java.text.BreakIterator#previous() + */ + @Override + public int previous() { + return wrapped.previous(); + } + + /* + * (non-Javadoc) + * + * @see java.text.BreakIterator#setText(java.text.CharacterIterator) + */ + @Override + public void setText(CharacterIterator newText) { + // call a method to check if null pointer + newText.current(); + wrapped.setText(newText); + } + + /* + * (non-Javadoc) + * + * @see java.text.BreakIterator#isBoundary(int) + */ + @Override + public boolean isBoundary(int offset) { + validateOffset(offset); + return wrapped.isBoundary(offset); + } + + /* + * (non-Javadoc) + * + * @see java.text.BreakIterator#preceding(int) + */ + @Override + public int preceding(int offset) { + validateOffset(offset); + return wrapped.preceding(offset); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof RuleBasedBreakIterator)) { + return false; + } + return wrapped.equals(((RuleBasedBreakIterator) o).wrapped); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return wrapped.toString(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return wrapped.hashCode(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#clone() + */ + @Override + public Object clone() { + RuleBasedBreakIterator cloned = (RuleBasedBreakIterator) super.clone(); + cloned.wrapped = (com.ibm.icu4jni.text.RuleBasedBreakIterator) wrapped + .clone(); + return cloned; + } + +} diff --git a/text/src/main/java/java/text/RuleBasedCollator.java b/text/src/main/java/java/text/RuleBasedCollator.java new file mode 100644 index 0000000..41a51e2 --- /dev/null +++ b/text/src/main/java/java/text/RuleBasedCollator.java @@ -0,0 +1,498 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** +******************************************************************************* +* Copyright (C) 1996-2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc and some of the method descriptions are copied from ICU4J +// source files. Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// The icu implementation used was changed from icu4j to icu4jni. +// END android-note + +package java.text; + +import org.apache.harmony.text.internal.nls.Messages; + +/** + * A concrete implementation class for {@code Collation}. + * <p> + * {@code RuleBasedCollator} has the following restrictions for efficiency + * (other subclasses may be used for more complex languages): + * </p> + * <ol> + * <li> If a French secondary ordering is specified it applies to the whole + * collator object.</li> + * <li> All non-mentioned Unicode characters are at the end of the collation + * order.</li> + * <li> If a character is not located in the {@code RuleBasedCollator}, the + * default Unicode Collation Algorithm (UCA) rulebased table is automatically + * searched as a backup.</li> + * </ol> + * <p> + * The collation table is composed of a list of collation rules, where each rule + * is of three forms: + * </p> + * <blockquote> + * + * <pre> + * <modifier> + * <relation> <text-argument> + * <reset> <text-argument> + * </pre> + * + * </blockquote> + * <p> + * The rule elements are defined as follows: + * </p> + * <ul type="disc"> + * <li><strong>Text-Argument</strong>: A text-argument is any sequence of + * characters, excluding special characters (that is, common whitespace + * characters [0009-000D, 0020] and rule syntax characters [0021-002F, + * 003A-0040, 005B-0060, 007B-007E]). If those characters are desired, you can + * put them in single quotes (for example, use '&' for ampersand). Note that + * unquoted white space characters are ignored; for example, {@code b c} is + * treated as {@code bc}.</li> + * <li><strong>Modifier</strong>: There is a single modifier which is used to + * specify that all accents (secondary differences) are backwards. + * <p> + * '@' : Indicates that accents are sorted backwards, as in French. + * </p> + * </li> + * <li><strong>Relation</strong>: The relations are the following: + * <ul type=square> + * <li>'<' : Greater, as a letter difference (primary) + * <li>';' : Greater, as an accent difference (secondary) + * <li>',' : Greater, as a case difference (tertiary) + * <li>'=' : Equal + * </ul> + * </li> + * <li><strong>Reset</strong>: There is a single reset which is used primarily + * for contractions and expansions, but which can also be used to add a + * modification at the end of a set of rules. + * <p> + * '&' : Indicates that the next rule follows the position to where the reset + * text-argument would be sorted. + * </p> + * </li> + * </ul> + * <p> + * This sounds more complicated than it is in practice. For example, the + * following are equivalent ways of expressing the same thing: + * </p> + * <blockquote> + * + * <pre> + * a < b < c + * a < b & b < c + * a < c & a < b + * </pre> + * + * </blockquote> + * <p> + * Notice that the order is important, as the subsequent item goes immediately + * after the text-argument. The following are not equivalent: + * </p> + * <blockquote> + * + * <pre> + * a < b & a < c + * a < c & a < b + * </pre> + * + * </blockquote> + * <p> + * Either the text-argument must already be present in the sequence, or some + * initial substring of the text-argument must be present. For example + * {@code "a < b & ae < e"} is valid since "a" is present in the sequence before + * "ae" is reset. In this latter case, "ae" is not entered and treated as a + * single character; instead, "e" is sorted as if it were expanded to two + * characters: "a" followed by an "e". This difference appears in natural + * languages: in traditional Spanish "ch" is treated as if it contracts to a + * single character (expressed as {@code "c < ch < d"}), while in traditional + * German a-umlaut is treated as if it expands to two characters (expressed as + * {@code "a,A < b,B ... & ae;\u00e3 & AE;\u00c3"}, where \u00e3 and \u00c3 + * are the escape sequences for a-umlaut). + * </p> + * <h4>Ignorable Characters</h4> + * <p> + * For ignorable characters, the first rule must start with a relation (the + * examples we have used above are really fragments; {@code "a < b"} really + * should be {@code "< a < b"}). If, however, the first relation is not + * {@code "<"}, then all text-arguments up to the first {@code "<"} are + * ignorable. For example, {@code ", - < a < b"} makes {@code "-"} an ignorable + * character. + * </p> + * <h4>Normalization and Accents</h4> + * <p> + * {@code RuleBasedCollator} automatically processes its rule table to include + * both pre-composed and combining-character versions of accented characters. + * Even if the provided rule string contains only base characters and separate + * combining accent characters, the pre-composed accented characters matching + * all canonical combinations of characters from the rule string will be entered + * in the table. + * </p> + * <p> + * This allows you to use a RuleBasedCollator to compare accented strings even + * when the collator is set to NO_DECOMPOSITION. However, if the strings to be + * collated contain combining sequences that may not be in canonical order, you + * should set the collator to CANONICAL_DECOMPOSITION to enable sorting of + * combining sequences. For more information, see <a + * href="http://www.aw.com/devpress">The Unicode Standard, Version 3.0</a>. + * </p> + * <h4>Errors</h4> + * <p> + * The following rules are not valid: + * </p> + * <ul type="disc"> + * <li>A text-argument contains unquoted punctuation symbols, for example + * {@code "a < b-c < d"}.</li> + * <li>A relation or reset character is not followed by a text-argument, for + * example {@code "a < , b"}.</li> + * <li>A reset where the text-argument (or an initial substring of the + * text-argument) is not already in the sequence or allocated in the default UCA + * table, for example {@code "a < b & e < f"}.</li> + * </ul> + * <p> + * If you produce one of these errors, {@code RuleBasedCollator} throws a + * {@code ParseException}. + * </p> + * <h4>Examples</h4> + * <p> + * Normally, to create a rule-based collator object, you will use + * {@code Collator}'s factory method {@code getInstance}. However, to create a + * rule-based collator object with specialized rules tailored to your needs, you + * construct the {@code RuleBasedCollator} with the rules contained in a + * {@code String} object. For example: + * </p> + * <blockquote> + * + * <pre> + * String Simple = "< a < b < c < d"; + * + * RuleBasedCollator mySimple = new RuleBasedCollator(Simple); + * </pre> + * + * </blockquote> + * <p> + * Or: + * </p> + * <blockquote> + * + * <pre> + * String Norwegian = "< a,A< b,B< c,C< d,D< e,E< f,F< g,G< h,H< i,I" + * + "< j,J< k,K< l,L< m,M< n,N< o,O< p,P< q,Q< r,R" + * + "< s,S< t,T< u,U< v,V< w,W< x,X< y,Y< z,Z" + * + "< \u00E5=a\u030A,\u00C5=A\u030A" + * + ";aa,AA< \u00E6,\u00C6< \u00F8,\u00D8"; + * + * RuleBasedCollator myNorwegian = new RuleBasedCollator(Norwegian); + * </pre> + * + * </blockquote> + * <p> + * Combining {@code Collator}s is as simple as concatenating strings. Here is + * an example that combines two {@code Collator}s from two different locales: + * </p> + * <blockquote> + * + * <pre> + * // Create an en_US Collator object + * RuleBasedCollator en_USCollator = (RuleBasedCollator)Collator + * .getInstance(new Locale("en", "US", "")); + * + * // Create a da_DK Collator object + * RuleBasedCollator da_DKCollator = (RuleBasedCollator)Collator + * .getInstance(new Locale("da", "DK", "")); + * + * // Combine the two collators + * // First, get the collation rules from en_USCollator + * String en_USRules = en_USCollator.getRules(); + * + * // Second, get the collation rules from da_DKCollator + * String da_DKRules = da_DKCollator.getRules(); + * + * RuleBasedCollator newCollator = new RuleBasedCollator(en_USRules + da_DKRules); + * // newCollator has the combined rules + * </pre> + * + * </blockquote> + * <p> + * The next example shows to make changes on an existing table to create a new + * {@code Collator} object. For example, add {@code "& C < ch, cH, Ch, CH"} to + * the {@code en_USCollator} object to create your own: + * </p> + * <blockquote> + * + * <pre> + * // Create a new Collator object with additional rules + * String addRules = "& C < ch, cH, Ch, CH"; + * + * RuleBasedCollator myCollator = new RuleBasedCollator(en_USCollator + addRules); + * // myCollator contains the new rules + * </pre> + * + * </blockquote> + * <p> + * The following example demonstrates how to change the order of non-spacing + * accents: + * </p> + * <blockquote> + * + * <pre> + * // old rule + * String oldRules = "= \u00a8 ; \u00af ; \u00bf" + "< a , A ; ae, AE ; \u00e6 , \u00c6" + * + "< b , B < c, C < e, E & C < d, D"; + * + * // change the order of accent characters + * String addOn = "& \u00bf ; \u00af ; \u00a8;"; + * + * RuleBasedCollator myCollator = new RuleBasedCollator(oldRules + addOn); + * </pre> + * + * </blockquote> + * <p> + * The last example shows how to put new primary ordering in before the default + * setting. For example, in the Japanese {@code Collator}, you can either sort + * English characters before or after Japanese characters: + * </p> + * <blockquote> + * + * <pre> + * // get en_US Collator rules + * RuleBasedCollator en_USCollator = (RuleBasedCollator) + * Collator.getInstance(Locale.US); + * + * // add a few Japanese character to sort before English characters + * // suppose the last character before the first base letter 'a' in + * // the English collation rule is \u30A2 + * String jaString = "& \u30A2 , \u30FC < \u30C8"; + * + * RuleBasedCollator myJapaneseCollator = + * new RuleBasedCollator(en_USCollator.getRules() + jaString); + * </pre> + * + * </blockquote> + * + * @since Android 1.0 + */ +public class RuleBasedCollator extends Collator { + + RuleBasedCollator(com.ibm.icu4jni.text.Collator wrapper) { + super(wrapper); + } + + /** + * Constructs a new instance of {@code RuleBasedCollator} using the + * specified {@code rules}. The {@code rules} are usually either + * hand-written based on the {@link RuleBasedCollator class description} or + * the result of a former {@link #getRules()} call. + * <p> + * Note that the {@code rules} are actually interpreted as a delta to the + * standard Unicode Collation Algorithm (UCA). Hence, an empty {@code rules} + * string results in the default UCA rules being applied. This differs + * slightly from other implementations which work with full {@code rules} + * specifications and may result in different behavior. + * + * @param rules + * the collation rules. + * @throws NullPointerException + * if {@code rules} is {@code null}. + * @throws ParseException + * if {@code rules} contains rules with invalid collation rule + * syntax. + * @since Android 1.0 + */ + public RuleBasedCollator(String rules) throws ParseException { + if (rules == null) { + throw new NullPointerException(); + } + // BEGIN android-removed + // if (rules.length() == 0) { + // // text.06=Build rules empty + // throw new ParseException(Messages.getString("text.06"), 0); //$NON-NLS-1$ + // } + // END andriod-removed + + try { + this.icuColl = new com.ibm.icu4jni.text.RuleBasedCollator(rules); + // BEGIN android-added + this.icuColl.setDecomposition( + com.ibm.icu4jni.text.Collator.CANONICAL_DECOMPOSITION); + // END android-added + } catch (Exception e) { + if (e instanceof ParseException) { + throw (ParseException) e; + } + /* + * -1 means it's not a ParseException. Maybe IOException thrown when + * an error occured while reading internal data. + */ + throw new ParseException(e.getMessage(), -1); + } + } + + /** + * Obtains a {@code CollationElementIterator} for the given + * {@code CharacterIterator}. The source iterator's integrity will be + * preserved since a new copy will be created for use. + * + * @param source + * the source character iterator. + * @return a {@code CollationElementIterator} for {@code source}. + * @since Android 1.0 + */ + public CollationElementIterator getCollationElementIterator( + CharacterIterator source) { + if (source == null) { + throw new NullPointerException(); + } + return new CollationElementIterator( + ((com.ibm.icu4jni.text.RuleBasedCollator) this.icuColl) + .getCollationElementIterator(source)); + } + + /** + * Obtains a {@code CollationElementIterator} for the given string. + * + * @param source + * the source string. + * @return the {@code CollationElementIterator} for {@code source}. + * @since Android 1.0 + */ + public CollationElementIterator getCollationElementIterator(String source) { + if (source == null) { + throw new NullPointerException(); + } + return new CollationElementIterator( + ((com.ibm.icu4jni.text.RuleBasedCollator) this.icuColl) + .getCollationElementIterator(source)); + } + + /** + * Returns the collation rules of this collator. These {@code rules} can be + * fed into the {@link #RuleBasedCollator(String)} constructor. + * <p> + * Note that the {@code rules} are actually interpreted as a delta to the + * standard Unicode Collation Algorithm (UCA). Hence, an empty {@code rules} + * string results in the default UCA rules being applied. This differs + * slightly from other implementations which work with full {@code rules} + * specifications and may result in different behavior. + * + * @return the collation rules. + * @since Android 1.0 + */ + public String getRules() { + return ((com.ibm.icu4jni.text.RuleBasedCollator) this.icuColl).getRules(); + } + + /** + * Returns a new collator with the same collation rules, decomposition mode and + * strength value as this collator. + * + * @return a shallow copy of this collator. + * @see java.lang.Cloneable + * @since Android 1.0 + */ + @Override + public Object clone() { + RuleBasedCollator clone = (RuleBasedCollator) super.clone(); + return clone; + } + + /** + * Compares the {@code source} text to the {@code target} text according to + * the collation rules, strength and decomposition mode for this + * {@code RuleBasedCollator}. See the {@code Collator} class description + * for an example of use. + * <p> + * General recommendation: If comparisons are to be done with the same strings + * multiple times, it is more efficient to generate {@code CollationKey} + * objects for the strings and use + * {@code CollationKey.compareTo(CollationKey)} for the comparisons. If each + * string is compared to only once, using + * {@code RuleBasedCollator.compare(String, String)} has better performance. + * </p> + * + * @param source + * the source text. + * @param target + * the target text. + * @return an integer which may be a negative value, zero, or else a + * positive value depending on whether {@code source} is less than, + * equivalent to, or greater than {@code target}. + * @since Android 1.0 + */ + @Override + public int compare(String source, String target) { + if (source == null || target == null) { + // text.08=one of arguments is null + throw new NullPointerException(Messages.getString("text.08")); //$NON-NLS-1$ + } + return this.icuColl.compare(source, target); + } + + /** + * Returns the {@code CollationKey} for the given source text. + * + * @param source + * the specified source text. + * @return the {@code CollationKey} for the given source text. + * @since Android 1.0 + */ + @Override + public CollationKey getCollationKey(String source) { + com.ibm.icu4jni.text.CollationKey icuKey = this.icuColl + .getCollationKey(source); + if (icuKey == null) { + return null; + } + return new CollationKey(source, icuKey); + } + + @Override + public int hashCode() { + return ((com.ibm.icu4jni.text.RuleBasedCollator) this.icuColl).getRules() + .hashCode(); + } + + /** + * Compares the specified object with this {@code RuleBasedCollator} and + * indicates if they are equal. In order to be equal, {@code object} must be + * an instance of {@code Collator} with the same collation rules and the + * same attributes. + * + * @param obj + * the object to compare with this object. + * @return {@code true} if the specified object is equal to this + * {@code RuleBasedCollator}; {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Collator)) { + return false; + } + return super.equals(obj); + } +} diff --git a/text/src/main/java/java/text/SimpleDateFormat.java b/text/src/main/java/java/text/SimpleDateFormat.java new file mode 100644 index 0000000..c78f29e --- /dev/null +++ b/text/src/main/java/java/text/SimpleDateFormat.java @@ -0,0 +1,1381 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** +******************************************************************************* +* Copyright (C) 1996-2007, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +// BEGIN android-note +// The class javadoc and some of the method descriptions are copied from ICU4J +// source files. Changes have been made to the copied descriptions. +// The icu license header was added to this file. +// END android-note + +package java.text; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.SimpleTimeZone; +import java.util.TimeZone; +import java.util.Vector; + +import org.apache.harmony.text.internal.nls.Messages; + +/** + * A concrete class for formatting and parsing dates in a locale-sensitive + * manner. It allows for formatting (date to text), parsing (text to date) and + * normalization. + * <p> + * {@code SimpleDateFormat} allows you to start by choosing any user-defined + * patterns for date-time formatting. However, you are encouraged to create a + * date-time formatter with either {@code getTimeInstance}, {@code + * getDateInstance}, or {@code getDateTimeInstance} in {@code DateFormat}. Each + * of these class methods can return a date/time formatter initialized with a + * default format pattern. You may modify the format pattern using the {@code + * applyPattern} methods as desired. For more information on using these + * methods, see {@link DateFormat}. + * </p> + * <h4>Time Format Syntax</h4> + * <p> + * To specify the time format, use a <em>time pattern</em> string. In this + * pattern, all ASCII letters are reserved as pattern letters, which are defined + * as follows: + * </p> + * <table border=0 cellspacing=3 cellpadding=0> + * <tr bgcolor="#ccccff"> + * <th>Symbol</th> + * <th>Meaning</th> + * <th>Presentation</th> + * <th>Example</th> + * </tr> + * <tr valign=top> + * <td>G</td> + * <td>era designator</td> + * <td>(Text)</td> + * <td>AD</td> + * </tr> + * <tr valign=top bgcolor="#eeeeff"> + * <td>y</td> + * <td>year</td> + * <td>(Number)</td> + * <td>1996</td> + * </tr> + * <tr valign=top> + * <td>M</td> + * <td>month in year</td> + * <td>(Text & Number)</td> + * <td>July & 07</td> + * </tr> + * <tr valign=top bgcolor="#eeeeff"> + * <td>d</td> + * <td>day in month</td> + * <td>(Number)</td> + * <td>10</td> + * </tr> + * <tr valign=top> + * <td>h</td> + * <td>hour in am/pm (1˜12)</td> + * <td>(Number)</td> + * <td>12</td> + * </tr> + * <tr valign=top bgcolor="#eeeeff"> + * <td>H</td> + * <td>hour in day (0˜23)</td> + * <td>(Number)</td> + * <td>0</td> + * </tr> + * <tr valign=top> + * <td>m</td> + * <td>minute in hour</td> + * <td>(Number)</td> + * <td>30</td> + * </tr> + * <tr valign=top bgcolor="#eeeeff"> + * <td>s</td> + * <td>second in minute</td> + * <td>(Number)</td> + * <td>55</td> + * </tr> + * <tr valign=top> + * <td>S</td> + * <td>fractional second</td> + * <td>(Number)</td> + * <td>978</td> + * </tr> + * <tr valign=top bgcolor="#eeeeff"> + * <td>E</td> + * <td>day of week</td> + * <td>(Text)</td> + * <td>Tuesday</td> + * </tr> + * <tr valign=top bgcolor="#eeeeff"> + * <td>D</td> + * <td>day in year</td> + * <td>(Number)</td> + * <td>189</td> + * </tr> + * <tr valign=top> + * <td>F</td> + * <td>day of week in month</td> + * <td>(Number)</td> + * <td>2 (2nd Wed in July)</td> + * </tr> + * <tr valign=top bgcolor="#eeeeff"> + * <td>w</td> + * <td>week in year</td> + * <td>(Number)</td> + * <td>27</td> + * </tr> + * <tr valign=top> + * <td>W</td> + * <td>week in month</td> + * <td>(Number)</td> + * <td>2</td> + * </tr> + * <tr valign=top bgcolor="#eeeeff"> + * <td>a</td> + * <td>am/pm marker</td> + * <td>(Text)</td> + * <td>PM</td> + * </tr> + * <tr valign=top> + * <td>k</td> + * <td>hour in day (1˜24)</td> + * <td>(Number)</td> + * <td>24</td> + * </tr> + * <tr valign=top bgcolor="#eeeeff"> + * <td>K</td> + * <td>hour in am/pm (0˜11)</td> + * <td>(Number)</td> + * <td>0</td> + * </tr> + * <tr valign=top> + * <td>z</td> + * <td>time zone</td> + * <td>(Text)</td> + * <td>Pacific Standard Time</td> + * </tr> + * <tr valign=top bgcolor="#eeeeff"> + * <td>Z</td> + * <td>time zone (RFC 822)</td> + * <td>(Number)</td> + * <td>-0800</td> + * </tr> + * <tr valign=top> + * <td>v</td> + * <td>time zone (generic)</td> + * <td>(Text)</td> + * <td>Pacific Time</td> + * </tr> + * <tr valign=top bgcolor="#eeeeff"> + * <td>V</td> + * <td>time zone (location)</td> + * <td>(Text)</td> + * <td>United States (Los Angeles)</td> + * </tr> + * <tr valign=top> + * <td>'</td> + * <td>escape for text</td> + * <td>(Delimiter)</td> + * <td>'Date='</td> + * </tr> + * <tr valign=top bgcolor="#eeeeff"> + * <td>''</td> + * <td>single quote</td> + * <td>(Literal)</td> + * <td>'o''clock'</td> + * </tr> + * </table> + * <p> + * The count of pattern letters determines the format: + * <p> + * <strong>(Text)</strong>: 4 or more pattern letters → use the full form, + * less than 4 pattern letters → use a short or abbreviated form if one + * exists. + * </p> + * <p> + * <strong>(Number)</strong>: the minimum number of digits. Shorter numbers are + * zero-padded to this amount. Year is handled specially; that is, if the count + * of 'y' is 2, the year will be truncated to 2 digits. (if "yyyy" produces + * "1997", "yy" produces "97".) Unlike other fields, fractional seconds are + * padded on the right with zero. + * <p> + * </p> + * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number. + * <p> + * Any characters in the pattern that are not in the ranges of ['a'..'z'] and + * ['A'..'Z'] will be treated as quoted text. For instance, characters like ':', + * '.', ' ', '#' and '@' will appear in the resulting time text even they are + * not embraced within single quotes. + * </p> + * <p> + * A pattern containing any invalid pattern letter will result in an exception + * thrown during formatting or parsing. + * </p> + * <h4>Examples Using the US Locale</h4> <blockquote> + * + * <pre> + * Format Pattern Result + * -------------- ------- + * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" → 1996.07.10 AD at 15:08:56 Pacific Time + * "EEE, MMM d, ''yy" → Wed, July 10, '96 + * "h:mm a" → 12:08 PM + * "hh 'o''clock' a, zzzz" → 12 o'clock PM, Pacific Daylight Time + * "K:mm a, vvv" → 0:00 PM, PT + * "yyyyy.MMMMM.dd GGG hh:mm aaa" → 01996.July.10 AD 12:08 PM + * </pre> + * + * </blockquote> <h4>Code Sample:</h4> <blockquote> + * + * <pre> + * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST"); + * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2 * 60 * 60 * 1000); + * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2 * 60 * 60 * 1000); + * + * // Format the current time. + * SimpleDateFormat formatter = new SimpleDateFormat( + * "yyyy.MM.dd G 'at' hh:mm:ss a zzz"); + * Date currentTime_1 = new Date(); + * String dateString = formatter.format(currentTime_1); + * + * // Parse the previous string back into a Date. + * ParsePosition pos = new ParsePosition(0); + * Date currentTime_2 = formatter.parse(dateString, pos); + * </pre> + * + * </blockquote> + * <p> + * In the example, the time value {@code currentTime_2} obtained from parsing + * will be equal to {@code currentTime_1}. However, they may not be equal if the + * am/pm marker 'a' is left out from the format pattern while the + * "hour in am/pm" pattern symbol is used. This information loss can happen when + * formatting the time in PM. + * </p> + * <p> + * When parsing a date string using the abbreviated year pattern ("yy"), {@code + * SimpleDateFormat} must interpret the abbreviated year relative to some + * century. It does this by adjusting dates to be within 80 years before and 20 + * years after the time the {@code SimpleDateFormat} instance is created. For + * example, using a pattern of "MM/dd/yy" and a {@code SimpleDateFormat} + * instance created on Jan 1, 1997, the string "01/11/12" would be interpreted + * as Jan 11, 2012 while the string "05/04/64" would be interpreted as May 4, + * 1964. During parsing, only strings consisting of exactly two digits, as + * defined by {@link java.lang.Character#isDigit(char)}, will be parsed into the + * default century. Any other numeric string, such as a one digit string, a + * three or more digit string, or a two digit string that isn't all digits (for + * example, "-1"), is interpreted literally. So "01/02/3" or "01/02/003" are + * parsed, using the same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is + * parsed as Jan 2, 4 BC. + * </p> + * <p> + * If the year pattern does not have exactly two 'y' characters, the year is + * interpreted literally, regardless of the number of digits. So using the + * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D. + * </p> + * <p> + * When numeric fields are adjacent directly, with no intervening delimiter + * characters, they constitute a run of adjacent numeric fields. Such runs are + * parsed specially. For example, the format "HHmmss" parses the input text + * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to + * parse "1234". In other words, the leftmost field of the run is flexible, + * while the others keep a fixed width. If the parse fails anywhere in the run, + * then the leftmost field is shortened by one character, and the entire run is + * parsed again. This is repeated until either the parse succeeds or the + * leftmost field is one character in length. If the parse still fails at that + * point, the parse of the run fails. + * </p> + * <p> + * For time zones that have no names, use the strings "GMT+hours:minutes" or + * "GMT-hours:minutes". + * </p> + * <p> + * The calendar defines the first day of the week, the first week of the year, + * whether hours are zero based or not (0 vs. 12 or 24) and the time zone. There + * is one common decimal format to handle all the numbers; the digit count is + * handled programmatically according to the pattern. + * <h4>Synchronization</h4> Date formats are not synchronized. It is recommended + * to create separate format instances for each thread. If multiple threads + * access a format concurrently, it must be synchronized externally. + * + * @see Calendar + * @see GregorianCalendar + * @see TimeZone + * @see DateFormat + * @see DateFormatSymbols + * @see DecimalFormat + * @since Android 1.0 + */ +public class SimpleDateFormat extends DateFormat { + + private static final long serialVersionUID = 4774881970558875024L; + + private static final String patternChars = "GyMdkHmsSEDFwWahKzZ"; //$NON-NLS-1$ + + private String pattern; + + private DateFormatSymbols formatData; + + transient private int creationYear; + + private Date defaultCenturyStart; + + /** + * Constructs a new {@code SimpleDateFormat} for formatting and parsing + * dates and times in the {@code SHORT} style for the default locale. + * + * @since Android 1.0 + */ + public SimpleDateFormat() { + this(Locale.getDefault()); + pattern = defaultPattern(); + formatData = new DateFormatSymbols(Locale.getDefault()); + } + + /** + * Constructs a new {@code SimpleDateFormat} using the specified + * non-localized pattern and the {@code DateFormatSymbols} and {@code + * Calendar} for the default locale. + * + * @param pattern + * the pattern. + * @exception IllegalArgumentException + * if {@code pattern} is not considered to be usable by this + * formatter. + * @since Android 1.0 + */ + public SimpleDateFormat(String pattern) { + this(pattern, Locale.getDefault()); + } + + /** + * Constructs a new {@code SimpleDateFormat} using the specified + * non-localized pattern and {@code DateFormatSymbols} and the {@code + * Calendar} for the default locale. + * + * @param template + * the pattern. + * @param value + * the DateFormatSymbols. + * @exception IllegalArgumentException + * if the pattern is invalid. + * @since Android 1.0 + */ + public SimpleDateFormat(String template, DateFormatSymbols value) { + this(Locale.getDefault()); + validatePattern(template); + pattern = template; + formatData = (DateFormatSymbols) value.clone(); + } + + /** + * Constructs a new {@code SimpleDateFormat} using the specified + * non-localized pattern and the {@code DateFormatSymbols} and {@code + * Calendar} for the specified locale. + * + * @param template + * the pattern. + * @param locale + * the locale. + * @exception IllegalArgumentException + * if the pattern is invalid. + * @since Android 1.0 + */ + public SimpleDateFormat(String template, Locale locale) { + this(locale); + validatePattern(template); + pattern = template; + formatData = new DateFormatSymbols(locale); + } + + private SimpleDateFormat(Locale locale) { + numberFormat = NumberFormat.getInstance(locale); + numberFormat.setParseIntegerOnly(true); + numberFormat.setGroupingUsed(false); + calendar = new GregorianCalendar(locale); + calendar.add(Calendar.YEAR, -80); + creationYear = calendar.get(Calendar.YEAR); + defaultCenturyStart = calendar.getTime(); + } + + private void append(StringBuffer buffer, FieldPosition position, + Vector<FieldPosition> fields, char format, int count) { + int field = -1; + int index = patternChars.indexOf(format); + if (index == -1) { + // text.03=Unknown pattern character - '{0}' + throw new IllegalArgumentException(Messages.getString( + "text.03", format)); //$NON-NLS-1$ + } + + int beginPosition = buffer.length(); + Field dateFormatField = null; + + switch (index) { + case ERA_FIELD: + dateFormatField = Field.ERA; + buffer.append(formatData.eras[calendar.get(Calendar.ERA)]); + break; + case YEAR_FIELD: + dateFormatField = Field.YEAR; + int year = calendar.get(Calendar.YEAR); + if (count < 4) { + appendNumber(buffer, 2, year %= 100); + } else { + appendNumber(buffer, count, year); + } + break; + case MONTH_FIELD: + dateFormatField = Field.MONTH; + int month = calendar.get(Calendar.MONTH); + if (count <= 2) { + appendNumber(buffer, count, month + 1); + } else if (count == 3) { + buffer.append(formatData.shortMonths[month]); + } else { + buffer.append(formatData.months[month]); + } + break; + case DATE_FIELD: + dateFormatField = Field.DAY_OF_MONTH; + field = Calendar.DATE; + break; + case HOUR_OF_DAY1_FIELD: // k + dateFormatField = Field.HOUR_OF_DAY1; + int hour = calendar.get(Calendar.HOUR_OF_DAY); + appendNumber(buffer, count, hour == 0 ? 24 : hour); + break; + case HOUR_OF_DAY0_FIELD: // H + dateFormatField = Field.HOUR_OF_DAY0; + field = Calendar.HOUR_OF_DAY; + break; + case MINUTE_FIELD: + dateFormatField = Field.MINUTE; + field = Calendar.MINUTE; + break; + case SECOND_FIELD: + dateFormatField = Field.SECOND; + field = Calendar.SECOND; + break; + case MILLISECOND_FIELD: + dateFormatField = Field.MILLISECOND; + int value = calendar.get(Calendar.MILLISECOND); + appendNumber(buffer, count, value); + break; + case DAY_OF_WEEK_FIELD: + dateFormatField = Field.DAY_OF_WEEK; + int day = calendar.get(Calendar.DAY_OF_WEEK); + if (count < 4) { + buffer.append(formatData.shortWeekdays[day]); + } else { + buffer.append(formatData.weekdays[day]); + } + break; + case DAY_OF_YEAR_FIELD: + dateFormatField = Field.DAY_OF_YEAR; + field = Calendar.DAY_OF_YEAR; + break; + case DAY_OF_WEEK_IN_MONTH_FIELD: + dateFormatField = Field.DAY_OF_WEEK_IN_MONTH; + field = Calendar.DAY_OF_WEEK_IN_MONTH; + break; + case WEEK_OF_YEAR_FIELD: + dateFormatField = Field.WEEK_OF_YEAR; + field = Calendar.WEEK_OF_YEAR; + break; + case WEEK_OF_MONTH_FIELD: + dateFormatField = Field.WEEK_OF_MONTH; + field = Calendar.WEEK_OF_MONTH; + break; + case AM_PM_FIELD: + dateFormatField = Field.AM_PM; + buffer.append(formatData.ampms[calendar.get(Calendar.AM_PM)]); + break; + case HOUR1_FIELD: // h + dateFormatField = Field.HOUR1; + hour = calendar.get(Calendar.HOUR); + appendNumber(buffer, count, hour == 0 ? 12 : hour); + break; + case HOUR0_FIELD: // K + dateFormatField = Field.HOUR0; + field = Calendar.HOUR; + break; + case TIMEZONE_FIELD: // z + dateFormatField = Field.TIME_ZONE; + appendTimeZone(buffer, count, true); + break; + case (TIMEZONE_FIELD + 1): // Z + dateFormatField = Field.TIME_ZONE; + appendTimeZone(buffer, count, false); + break; + } + if (field != -1) { + appendNumber(buffer, count, calendar.get(field)); + } + + if (fields != null) { + position = new FieldPosition(dateFormatField); + position.setBeginIndex(beginPosition); + position.setEndIndex(buffer.length()); + fields.add(position); + } else { + // Set to the first occurrence + if ((position.getFieldAttribute() == dateFormatField || (position + .getFieldAttribute() == null && position.getField() == index)) + && position.getEndIndex() == 0) { + position.setBeginIndex(beginPosition); + position.setEndIndex(buffer.length()); + } + } + } + + private void appendTimeZone(StringBuffer buffer, int count, + boolean generalTimezone) { + // cannot call TimeZone.getDisplayName() because it would not use + // the DateFormatSymbols of this SimpleDateFormat + + if (generalTimezone) { + String id = calendar.getTimeZone().getID(); + // BEGIN android-changed + String[][] zones = formatData.internalZoneStrings(); + // END android-changed + String[] zone = null; + for (String[] element : zones) { + if (id.equals(element[0])) { + zone = element; + break; + } + } + if (zone == null) { + int offset = calendar.get(Calendar.ZONE_OFFSET) + + calendar.get(Calendar.DST_OFFSET); + char sign = '+'; + if (offset < 0) { + sign = '-'; + offset = -offset; + } + buffer.append("GMT"); //$NON-NLS-1$ + buffer.append(sign); + appendNumber(buffer, 2, offset / 3600000); + buffer.append(':'); + appendNumber(buffer, 2, (offset % 3600000) / 60000); + } else { + int daylight = calendar.get(Calendar.DST_OFFSET) == 0 ? 0 : 2; + if (count < 4) { + buffer.append(zone[2 + daylight]); + } else { + buffer.append(zone[1 + daylight]); + } + } + } else { + int offset = calendar.get(Calendar.ZONE_OFFSET) + + calendar.get(Calendar.DST_OFFSET); + char sign = '+'; + if (offset < 0) { + sign = '-'; + offset = -offset; + } + buffer.append(sign); + appendNumber(buffer, 2, offset / 3600000); + appendNumber(buffer, 2, (offset % 3600000) / 60000); + } + } + + private void appendNumber(StringBuffer buffer, int count, int value) { + int minimumIntegerDigits = numberFormat.getMinimumIntegerDigits(); + numberFormat.setMinimumIntegerDigits(count); + numberFormat.format(new Integer(value), buffer, new FieldPosition(0)); + numberFormat.setMinimumIntegerDigits(minimumIntegerDigits); + } + + /** + * Changes the pattern of this simple date format to the specified pattern + * which uses localized pattern characters. + * + * @param template + * the localized pattern. + * @since Android 1.0 + */ + public void applyLocalizedPattern(String template) { + pattern = convertPattern(template, formatData.getLocalPatternChars(), + patternChars, true); + } + + /** + * Changes the pattern of this simple date format to the specified pattern + * which uses non-localized pattern characters. + * + * @param template + * the non-localized pattern. + * @exception IllegalArgumentException + * if the pattern is invalid. + * @since Android 1.0 + */ + public void applyPattern(String template) { + validatePattern(template); + pattern = template; + } + + /** + * Returns a new {@code SimpleDateFormat} with the same pattern and + * properties as this simple date format. + * + * @return a shallow copy of this simple date format. + * @see java.lang.Cloneable + * @since Android 1.0 + */ + @Override + public Object clone() { + SimpleDateFormat clone = (SimpleDateFormat) super.clone(); + clone.formatData = (DateFormatSymbols) formatData.clone(); + clone.defaultCenturyStart = new Date(defaultCenturyStart.getTime()); + return clone; + } + + private static String defaultPattern() { + ResourceBundle bundle = getBundle(Locale.getDefault()); + String styleName = getStyleName(SHORT); + return bundle.getString("Date_" + styleName) + " " //$NON-NLS-1$ //$NON-NLS-2$ + + bundle.getString("Time_" + styleName); //$NON-NLS-1$ + } + + /** + * Compares the specified object with this simple date format and indicates + * if they are equal. In order to be equal, {@code object} must be an + * instance of {@code SimpleDateFormat} and have the same {@code DateFormat} + * properties, pattern, {@code DateFormatSymbols} and creation year. + * + * @param object + * the object to compare with this object. + * @return {@code true} if the specified object is equal to this simple date + * format; {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof SimpleDateFormat)) { + return false; + } + SimpleDateFormat simple = (SimpleDateFormat) object; + return super.equals(object) && pattern.equals(simple.pattern) + && formatData.equals(simple.formatData); + } + + private Date error(ParsePosition position, int offset, TimeZone zone) { + position.setErrorIndex(offset); + calendar.setTimeZone(zone); + return null; + } + + /** + * Formats the specified object using the rules of this simple date format + * and returns an {@code AttributedCharacterIterator} with the formatted + * date and attributes. + * + * @param object + * the object to format. + * @return an {@code AttributedCharacterIterator} with the formatted date + * and attributes. + * @exception IllegalArgumentException + * when the object cannot be formatted by this simple date + * format. + * @since Android 1.0 + */ + @Override + public AttributedCharacterIterator formatToCharacterIterator(Object object) { + if (object == null) { + throw new NullPointerException(); + } + if (object instanceof Date) { + return formatToCharacterIteratorImpl((Date) object); + } + if (object instanceof Number) { + return formatToCharacterIteratorImpl(new Date(((Number) object) + .longValue())); + } + throw new IllegalArgumentException(); + } + + private AttributedCharacterIterator formatToCharacterIteratorImpl(Date date) { + StringBuffer buffer = new StringBuffer(); + Vector<FieldPosition> fields = new Vector<FieldPosition>(); + + // format the date, and find fields + formatImpl(date, buffer, null, fields); + + // create and AttributedString with the formatted buffer + AttributedString as = new AttributedString(buffer.toString()); + + // add DateFormat field attributes to the AttributedString + for (int i = 0; i < fields.size(); i++) { + FieldPosition pos = fields.elementAt(i); + Format.Field attribute = pos.getFieldAttribute(); + as.addAttribute(attribute, attribute, pos.getBeginIndex(), pos + .getEndIndex()); + } + + // return the CharacterIterator from AttributedString + return as.getIterator(); + } + + /** + * Formats the specified date as a string using the pattern of this date + * format and appends the string to the specified string buffer. + * <p> + * If the {@code field} member of {@code field} contains a value specifying + * a format field, then its {@code beginIndex} and {@code endIndex} members + * will be updated with the position of the first occurrence of this field + * in the formatted text. + * </p> + * + * @param date + * the date to format. + * @param buffer + * the target string buffer to append the formatted date/time to. + * @param field + * on input: an optional alignment field; on output: the offsets + * of the alignment field in the formatted text. + * @return the string buffer. + * @throws IllegalArgumentException + * if there are invalid characters in the pattern. + * @since Android 1.0 + */ + @Override + public StringBuffer format(Date date, StringBuffer buffer, + FieldPosition field) { + return formatImpl(date, buffer, field, null); + } + + /** + * Validates the format character. + * + * @param format + * the format character + * + * @throws IllegalArgumentException + * when the format character is invalid + */ + private void validateFormat(char format) { + int index = patternChars.indexOf(format); + if (index == -1) { + // text.03=Unknown pattern character - '{0}' + throw new IllegalArgumentException(Messages.getString( + "text.03", format)); //$NON-NLS-1$ + } + } + + /** + * Validates the pattern. + * + * @param template + * the pattern to validate. + * + * @throws NullPointerException + * if the pattern is null + * @throws IllegalArgumentException + * if the pattern is invalid + */ + private void validatePattern(String template) { + boolean quote = false; + int next, last = -1, count = 0; + + final int patternLength = template.length(); + for (int i = 0; i < patternLength; i++) { + next = (template.charAt(i)); + if (next == '\'') { + if (count > 0) { + validateFormat((char) last); + count = 0; + } + if (last == next) { + last = -1; + } else { + last = next; + } + quote = !quote; + continue; + } + if (!quote + && (last == next || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) { + if (last == next) { + count++; + } else { + if (count > 0) { + validateFormat((char) last); + } + last = next; + count = 1; + } + } else { + if (count > 0) { + validateFormat((char) last); + count = 0; + } + last = -1; + } + } + if (count > 0) { + validateFormat((char) last); + } + + if (quote) { + // text.04=Unterminated quote {0} + throw new IllegalArgumentException(Messages.getString("text.04")); //$NON-NLS-1$ + } + + } + + /** + * Formats the date. + * <p> + * If the FieldPosition {@code field} is not null, and the field + * specified by this FieldPosition is formatted, set the begin and end index + * of the formatted field in the FieldPosition. + * <p> + * If the Vector {@code fields} is not null, find fields of this + * date, set FieldPositions with these fields, and add them to the fields + * vector. + * + * @param date + * Date to Format + * @param buffer + * StringBuffer to store the resulting formatted String + * @param field + * FieldPosition to set begin and end index of the field + * specified, if it is part of the format for this date + * @param fields + * Vector used to store the FieldPositions for each field in this + * date + * + * @return the formatted Date + * + * @exception IllegalArgumentException + * when the object cannot be formatted by this Format + */ + private StringBuffer formatImpl(Date date, StringBuffer buffer, + FieldPosition field, Vector<FieldPosition> fields) { + + boolean quote = false; + int next, last = -1, count = 0; + calendar.setTime(date); + if (field != null) { + field.clear(); + } + + final int patternLength = pattern.length(); + for (int i = 0; i < patternLength; i++) { + next = (pattern.charAt(i)); + if (next == '\'') { + if (count > 0) { + append(buffer, field, fields, (char) last, count); + count = 0; + } + if (last == next) { + buffer.append('\''); + last = -1; + } else { + last = next; + } + quote = !quote; + continue; + } + if (!quote + && (last == next || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) { + if (last == next) { + count++; + } else { + if (count > 0) { + append(buffer, field, fields, (char) last, count); + } + last = next; + count = 1; + } + } else { + if (count > 0) { + append(buffer, field, fields, (char) last, count); + count = 0; + } + last = -1; + buffer.append((char) next); + } + } + if (count > 0) { + append(buffer, field, fields, (char) last, count); + } + return buffer; + } + + /** + * Returns the date which is the start of the one hundred year period for + * two digits year values. + * + * @return a date. + * @since Android 1.0 + */ + public Date get2DigitYearStart() { + return defaultCenturyStart; + } + + /** + * Returns the {@code DateFormatSymbols} used by this simple date format. + * + * @return the {@code DateFormatSymbols} object. + * @since Android 1.0 + */ + public DateFormatSymbols getDateFormatSymbols() { + // Return a clone so the arrays in the ResourceBundle are not modified + return (DateFormatSymbols) formatData.clone(); + } + + @Override + public int hashCode() { + return super.hashCode() + pattern.hashCode() + formatData.hashCode() + + creationYear; + } + + private int parse(String string, int offset, char format, int count) { + int index = patternChars.indexOf(format); + if (index == -1) { + // text.03=Unknown pattern character - '{0}' + throw new IllegalArgumentException(Messages.getString( + "text.03", format)); //$NON-NLS-1$ + } + int field = -1; + int absolute = 0; + if (count < 0) { + count = -count; + absolute = count; + } + switch (index) { + case ERA_FIELD: + return parseText(string, offset, formatData.eras, Calendar.ERA); + case YEAR_FIELD: + if (count >= 3) { + field = Calendar.YEAR; + } else { + ParsePosition position = new ParsePosition(offset); + Number result = parseNumber(absolute, string, position); + if (result == null) { + return -position.getErrorIndex() - 1; + } + int year = result.intValue(); + // A two digit year must be exactly two digits, i.e. 01 + if ((position.getIndex() - offset) == 2 && year >= 0) { + year += creationYear / 100 * 100; + if (year < creationYear) { + year += 100; + } + } + calendar.set(Calendar.YEAR, year); + return position.getIndex(); + } + break; + case MONTH_FIELD: + if (count <= 2) { + return parseNumber(absolute, string, offset, + Calendar.MONTH, -1); + } + index = parseText(string, offset, formatData.months, + Calendar.MONTH); + if (index < 0) { + return parseText(string, offset, formatData.shortMonths, + Calendar.MONTH); + } + return index; + case DATE_FIELD: + field = Calendar.DATE; + break; + case HOUR_OF_DAY1_FIELD: + ParsePosition position = new ParsePosition(offset); + Number result = parseNumber(absolute, string, position); + if (result == null) { + return -position.getErrorIndex() - 1; + } + int hour = result.intValue(); + if (hour == 24) { + hour = 0; + } + calendar.set(Calendar.HOUR_OF_DAY, hour); + return position.getIndex(); + case HOUR_OF_DAY0_FIELD: + field = Calendar.HOUR_OF_DAY; + break; + case MINUTE_FIELD: + field = Calendar.MINUTE; + break; + case SECOND_FIELD: + field = Calendar.SECOND; + break; + case MILLISECOND_FIELD: + field = Calendar.MILLISECOND; + break; + case DAY_OF_WEEK_FIELD: + index = parseText(string, offset, formatData.weekdays, + Calendar.DAY_OF_WEEK); + if (index < 0) { + return parseText(string, offset, formatData.shortWeekdays, + Calendar.DAY_OF_WEEK); + } + return index; + case DAY_OF_YEAR_FIELD: + field = Calendar.DAY_OF_YEAR; + break; + case DAY_OF_WEEK_IN_MONTH_FIELD: + field = Calendar.DAY_OF_WEEK_IN_MONTH; + break; + case WEEK_OF_YEAR_FIELD: + field = Calendar.WEEK_OF_YEAR; + break; + case WEEK_OF_MONTH_FIELD: + field = Calendar.WEEK_OF_MONTH; + break; + case AM_PM_FIELD: + return parseText(string, offset, formatData.ampms, + Calendar.AM_PM); + case HOUR1_FIELD: + position = new ParsePosition(offset); + result = parseNumber(absolute, string, position); + if (result == null) { + return -position.getErrorIndex() - 1; + } + hour = result.intValue(); + if (hour == 12) { + hour = 0; + } + calendar.set(Calendar.HOUR, hour); + return position.getIndex(); + case HOUR0_FIELD: + field = Calendar.HOUR; + break; + case TIMEZONE_FIELD: + return parseTimeZone(string, offset); + case (TIMEZONE_FIELD + 1): + return parseTimeZone(string, offset); + } + if (field != -1) { + return parseNumber(absolute, string, offset, field, 0); + } + return offset; + } + + /** + * Parses a date from the specified string starting at the index specified + * by {@code position}. If the string is successfully parsed then the index + * of the {@code ParsePosition} is updated to the index following the parsed + * text. On error, the index is unchanged and the error index of {@code + * ParsePosition} is set to the index where the error occurred. + * + * @param string + * the string to parse using the pattern of this simple date + * format. + * @param position + * input/output parameter, specifies the start index in {@code + * string} from where to start parsing. If parsing is successful, + * it is updated with the index following the parsed text; on + * error, the index is unchanged and the error index is set to + * the index where the error occurred. + * @return the date resulting from the parse, or {@code null} if there is an + * error. + * @throws IllegalArgumentException + * if there are invalid characters in the pattern. + * @since Android 1.0 + */ + @Override + public Date parse(String string, ParsePosition position) { + boolean quote = false; + int next, last = -1, count = 0, offset = position.getIndex(); + int length = string.length(); + calendar.clear(); + TimeZone zone = calendar.getTimeZone(); + final int patternLength = pattern.length(); + for (int i = 0; i < patternLength; i++) { + next = pattern.charAt(i); + if (next == '\'') { + if (count > 0) { + if ((offset = parse(string, offset, (char) last, count)) < 0) { + return error(position, -offset - 1, zone); + } + count = 0; + } + if (last == next) { + if (offset >= length || string.charAt(offset) != '\'') { + return error(position, offset, zone); + } + offset++; + last = -1; + } else { + last = next; + } + quote = !quote; + continue; + } + if (!quote + && (last == next || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) { + if (last == next) { + count++; + } else { + if (count > 0) { + if ((offset = parse(string, offset, (char) last, -count)) < 0) { + return error(position, -offset - 1, zone); + } + } + last = next; + count = 1; + } + } else { + if (count > 0) { + if ((offset = parse(string, offset, (char) last, count)) < 0) { + return error(position, -offset - 1, zone); + } + count = 0; + } + last = -1; + if (offset >= length || string.charAt(offset) != next) { + return error(position, offset, zone); + } + offset++; + } + } + if (count > 0) { + if ((offset = parse(string, offset, (char) last, count)) < 0) { + return error(position, -offset - 1, zone); + } + } + Date date; + try { + date = calendar.getTime(); + } catch (IllegalArgumentException e) { + return error(position, offset, zone); + } + position.setIndex(offset); + calendar.setTimeZone(zone); + return date; + } + + private Number parseNumber(int max, String string, ParsePosition position) { + int digit, length = string.length(), result = 0; + int index = position.getIndex(); + if (max > 0 && max < length - index) { + length = index + max; + } + while (index < length + && (string.charAt(index) == ' ' || string.charAt(index) == '\t')) { + index++; + } + if (max == 0) { + position.setIndex(index); + return numberFormat.parse(string, position); + } + + while (index < length + && (digit = Character.digit(string.charAt(index), 10)) != -1) { + index++; + result = result * 10 + digit; + } + if (index == position.getIndex()) { + position.setErrorIndex(index); + return null; + } + position.setIndex(index); + return new Integer(result); + } + + private int parseNumber(int max, String string, int offset, int field, + int skew) { + ParsePosition position = new ParsePosition(offset); + Number result = parseNumber(max, string, position); + if (result == null) { + return -position.getErrorIndex() - 1; + } + calendar.set(field, result.intValue() + skew); + return position.getIndex(); + } + + private int parseText(String string, int offset, String[] text, int field) { + int found = -1; + for (int i = 0; i < text.length; i++) { + if (text[i].length() == 0) { + continue; + } + if (string + .regionMatches(true, offset, text[i], 0, text[i].length())) { + // Search for the longest match, in case some fields are subsets + if (found == -1 || text[i].length() > text[found].length()) { + found = i; + } + } + } + if (found != -1) { + calendar.set(field, found); + return offset + text[found].length(); + } + return -offset - 1; + } + + private int parseTimeZone(String string, int offset) { + // BEGIN android-changed + String[][] zones = formatData.internalZoneStrings(); + // END android-changed + boolean foundGMT = string.regionMatches(offset, "GMT", 0, 3); //$NON-NLS-1$ + if (foundGMT) { + offset += 3; + } + char sign; + if (offset < string.length() + && ((sign = string.charAt(offset)) == '+' || sign == '-')) { + ParsePosition position = new ParsePosition(offset + 1); + Number result = numberFormat.parse(string, position); + if (result == null) { + return -position.getErrorIndex() - 1; + } + int hour = result.intValue(); + int raw = hour * 3600000; + int index = position.getIndex(); + if (index < string.length() && string.charAt(index) == ':') { + position.setIndex(index + 1); + result = numberFormat.parse(string, position); + if (result == null) { + return -position.getErrorIndex() - 1; + } + int minute = result.intValue(); + raw += minute * 60000; + } else if (hour >= 24) { + raw = (hour / 100 * 3600000) + (hour % 100 * 60000); + } + if (sign == '-') { + raw = -raw; + } + calendar.setTimeZone(new SimpleTimeZone(raw, "")); //$NON-NLS-1$ + return position.getIndex(); + } + if (foundGMT) { + calendar.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$ + return offset; + } + for (String[] element : zones) { + for (int j = 1; j < 5; j++) { + if (string.regionMatches(true, offset, element[j], 0, + element[j].length())) { + TimeZone zone = TimeZone.getTimeZone(element[0]); + if (zone == null) { + return -offset - 1; + } + int raw = zone.getRawOffset(); + if (j >= 3 && zone.useDaylightTime()) { + raw += 3600000; + } + calendar.setTimeZone(new SimpleTimeZone(raw, "")); //$NON-NLS-1$ + return offset + element[j].length(); + } + } + } + return -offset - 1; + } + + /** + * Sets the date which is the start of the one hundred year period for two + * digits year values. + * + * @param date + * the new date. + * @since Android 1.0 + */ + public void set2DigitYearStart(Date date) { + defaultCenturyStart = date; + Calendar cal = new GregorianCalendar(); + cal.setTime(date); + creationYear = cal.get(Calendar.YEAR); + } + + /** + * Sets the {@code DateFormatSymbols} used by this simple date format. + * + * @param value + * the new {@code DateFormatSymbols} object. + * @since Android 1.0 + */ + public void setDateFormatSymbols(DateFormatSymbols value) { + formatData = (DateFormatSymbols) value.clone(); + } + + /** + * Returns the pattern of this simple date format using localized pattern + * characters. + * + * @return the localized pattern. + * @since Android 1.0 + */ + public String toLocalizedPattern() { + return convertPattern(pattern, patternChars, formatData + .getLocalPatternChars(), false); + } + + /** + * Returns the pattern of this simple date format using non-localized + * pattern characters. + * + * @return the non-localized pattern. + * @since Android 1.0 + */ + public String toPattern() { + return pattern; + } + + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("defaultCenturyStart", Date.class), //$NON-NLS-1$ + new ObjectStreamField("formatData", DateFormatSymbols.class), //$NON-NLS-1$ + new ObjectStreamField("pattern", String.class), //$NON-NLS-1$ + new ObjectStreamField("serialVersionOnStream", Integer.TYPE), }; //$NON-NLS-1$ + + private void writeObject(ObjectOutputStream stream) throws IOException { + ObjectOutputStream.PutField fields = stream.putFields(); + fields.put("defaultCenturyStart", defaultCenturyStart); //$NON-NLS-1$ + fields.put("formatData", formatData); //$NON-NLS-1$ + fields.put("pattern", pattern); //$NON-NLS-1$ + fields.put("serialVersionOnStream", 1); //$NON-NLS-1$ + stream.writeFields(); + } + + private void readObject(ObjectInputStream stream) throws IOException, + ClassNotFoundException { + ObjectInputStream.GetField fields = stream.readFields(); + int version = fields.get("serialVersionOnStream", 0); //$NON-NLS-1$ + Date date; + if (version > 0) { + date = (Date) fields.get("defaultCenturyStart", new Date()); //$NON-NLS-1$ + } else { + date = new Date(); + } + set2DigitYearStart(date); + formatData = (DateFormatSymbols) fields.get("formatData", null); //$NON-NLS-1$ + pattern = (String) fields.get("pattern", ""); //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/text/src/main/java/java/text/StringCharacterIterator.java b/text/src/main/java/java/text/StringCharacterIterator.java new file mode 100644 index 0000000..5d02ceb --- /dev/null +++ b/text/src/main/java/java/text/StringCharacterIterator.java @@ -0,0 +1,287 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.text; + +/** + * An implementation of {@link CharacterIterator} for strings. + * + * @since Android 1.0 + */ +public final class StringCharacterIterator implements CharacterIterator { + + String string; + + int start, end, offset; + + /** + * Constructs a new {@code StringCharacterIterator} on the specified string. + * The begin and current indices are set to the beginning of the string, the + * end index is set to the length of the string. + * + * @param value + * the source string to iterate over. + * @since Android 1.0 + */ + public StringCharacterIterator(String value) { + string = value; + start = offset = 0; + end = string.length(); + } + + /** + * Constructs a new {@code StringCharacterIterator} on the specified string + * with the current index set to the specified value. The begin index is set + * to the beginning of the string, the end index is set to the length of the + * string. + * + * @param value + * the source string to iterate over. + * @param location + * the current index. + * @exception IllegalArgumentException + * if {@code location} is negative or greater than the length + * of the source string. + * @since Android 1.0 + */ + public StringCharacterIterator(String value, int location) { + string = value; + start = 0; + end = string.length(); + if (location < 0 || location > end) { + throw new IllegalArgumentException(); + } + offset = location; + } + + /** + * Constructs a new {@code StringCharacterIterator} on the specified string + * with the begin, end and current index set to the specified values. + * + * @param value + * the source string to iterate over. + * @param start + * the index of the first character to iterate. + * @param end + * the index one past the last character to iterate. + * @param location + * the current index. + * @exception IllegalArgumentException + * if {@code start < 0}, {@code start > end}, + * {@code location < start}, {@code location > end} or if + * {@code end} is greater than the length of {@code value}. + * @since Android 1.0 + */ + public StringCharacterIterator(String value, int start, int end, + int location) { + string = value; + if (start < 0 || end > string.length() || start > end + || location < start || location > end) { + throw new IllegalArgumentException(); + } + this.start = start; + this.end = end; + offset = location; + } + + /** + * Returns a new {@code StringCharacterIterator} with the same source + * string, begin, end, and current index as this iterator. + * + * @return a shallow copy of this iterator. + * @see java.lang.Cloneable + * @since Android 1.0 + */ + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + /** + * Returns the character at the current index in the source string. + * + * @return the current character, or {@code DONE} if the current index is + * past the end. + * @since Android 1.0 + */ + public char current() { + if (offset == end) { + return DONE; + } + return string.charAt(offset); + } + + /** + * Compares the specified object with this {@code StringCharacterIterator} + * and indicates if they are equal. In order to be equal, {@code object} + * must be an instance of {@code StringCharacterIterator} that iterates over + * the same sequence of characters with the same index. + * + * @param object + * the object to compare with this object. + * @return {@code true} if the specified object is equal to this + * {@code StringCharacterIterator}; {@code false} otherwise. + * @see #hashCode + * @since Android 1.0 + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof StringCharacterIterator)) { + return false; + } + StringCharacterIterator it = (StringCharacterIterator) object; + return string.equals(it.string) && start == it.start && end == it.end + && offset == it.offset; + } + + /** + * Sets the current position to the begin index and returns the character at + * the new position in the source string. + * + * @return the character at the begin index or {@code DONE} if the begin + * index is equal to the end index. + * @since Android 1.0 + */ + public char first() { + if (start == end) { + return DONE; + } + offset = start; + return string.charAt(offset); + } + + /** + * Returns the begin index in the source string. + * + * @return the index of the first character of the iteration. + * @since Android 1.0 + */ + public int getBeginIndex() { + return start; + } + + /** + * Returns the end index in the source string. + * + * @return the index one past the last character of the iteration. + * @since Android 1.0 + */ + public int getEndIndex() { + return end; + } + + /** + * Returns the current index in the source string. + * + * @return the current index. + * @since Android 1.0 + */ + public int getIndex() { + return offset; + } + + @Override + public int hashCode() { + return string.hashCode() + start + end + offset; + } + + /** + * Sets the current position to the end index - 1 and returns the character + * at the new position. + * + * @return the character before the end index or {@code DONE} if the begin + * index is equal to the end index. + * @since Android 1.0 + */ + public char last() { + if (start == end) { + return DONE; + } + offset = end - 1; + return string.charAt(offset); + } + + /** + * Increments the current index and returns the character at the new index. + * + * @return the character at the next index, or {@code DONE} if the next + * index would be past the end. + * @since Android 1.0 + */ + public char next() { + if (offset >= (end - 1)) { + offset = end; + return DONE; + } + return string.charAt(++offset); + } + + /** + * Decrements the current index and returns the character at the new index. + * + * @return the character at the previous index, or {@code DONE} if the + * previous index would be past the beginning. + * @since Android 1.0 + */ + public char previous() { + if (offset == start) { + return DONE; + } + return string.charAt(--offset); + } + + /** + * Sets the current index in the source string. + * + * @param location + * the index the current position is set to. + * @return the character at the new index, or {@code DONE} if + * {@code location} is set to the end index. + * @exception IllegalArgumentException + * if {@code location} is smaller than the begin index or + * greater than the end index. + * @since Android 1.0 + */ + public char setIndex(int location) { + if (location < start || location > end) { + throw new IllegalArgumentException(); + } + offset = location; + if (offset == end) { + return DONE; + } + return string.charAt(offset); + } + + /** + * Sets the source string to iterate over. The begin and end positions are + * set to the start and end of this string. + * + * @param value + * the new source string. + * @since Android 1.0 + */ + public void setText(String value) { + string = value; + start = offset = 0; + end = value.length(); + } +} diff --git a/text/src/main/java/java/text/package.html b/text/src/main/java/java/text/package.html new file mode 100644 index 0000000..2bcd8f9 --- /dev/null +++ b/text/src/main/java/java/text/package.html @@ -0,0 +1,17 @@ +<html> + <body> + <p> + The java.text package allows to uncouple the text in an application + from natural languages. + </p> + <p> + By using the classes in this package, it is possible to write the + application in an unlocalized way. The benefit of this is that a new + localization can be provided at any time without having to change the + code. Support for localization is given for numbers, messages, dates and + other characteristics of a language like the directionality, sorting order + or enumeration of characters, words or lines. + </p> + @since Android 1.0 +</body> +</html> diff --git a/text/src/main/java/org/apache/harmony/text/BidiRun.java b/text/src/main/java/org/apache/harmony/text/BidiRun.java new file mode 100644 index 0000000..d1033f4 --- /dev/null +++ b/text/src/main/java/org/apache/harmony/text/BidiRun.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text; + +/** + * TODO: type description + */ +public class BidiRun { + private final int start; + + private final int limit; + + private final int level; + + public BidiRun(int start, int limit, int level) { + this.start = start; + this.limit = limit; + this.level = level; + } + + public int getLevel() { + return level; + } + + public int getLimit() { + return limit; + } + + public int getStart() { + return start; + } + + @Override + public boolean equals(Object o) { + return o == null || o.getClass() != BidiRun.class ? false + : this.start == ((BidiRun) o).start + && this.limit == ((BidiRun) o).limit + && this.level == ((BidiRun) o).level; + } +} diff --git a/text/src/main/java/org/apache/harmony/text/BidiWrapper.java b/text/src/main/java/org/apache/harmony/text/BidiWrapper.java new file mode 100644 index 0000000..240fcdf --- /dev/null +++ b/text/src/main/java/org/apache/harmony/text/BidiWrapper.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text; + +/** + * TODO: type description + */ + +public final class BidiWrapper { + + public static final int UBIDI_DEFAULT_LTR = 0xfe; + + public static final int UBIDI_DEFAULT_RTL = 0xff; + + public static final int UBIDI_MAX_EXPLICIT_LEVEL = 61; + + public static final int UBIDI_LEVEL_OVERRIDE = 0x80; + + public static final int UBIDI_KEEP_BASE_COMBINING = 1; + + public static final int UBIDI_DO_MIRRORING = 2; + + public static final int UBIDI_INSERT_LRM_FOR_NUMERIC = 4; + + public static final int UBIDI_REMOVE_BIDI_CONTROLS = 8; + + public static final int UBIDI_OUTPUT_REVERSE = 16; + + public static final int UBiDiDirection_UBIDI_LTR = 0; + + public static final int UBiDiDirection_UBIDI_RTL = 1; + + public static final int UBiDiDirection_UBIDI_MIXED = 2; + + // Allocate a UBiDi structure. + public static native long ubidi_open(); + + // ubidi_close() must be called to free the memory associated with a + // UBiDi object. + public static native void ubidi_close(long pBiDi); + + // Perform the Unicode BiDi algorithm. + public static native void ubidi_setPara(long pBiDi, char[] text, + int length, byte paraLevel, byte[] embeddingLevels); + + // ubidi_setLine() sets a UBiDi to contain the reordering information, + // especially the resolved levels, for all the characters in a line of + // text. + public static native long ubidi_setLine(final long pParaBiDi, int start, + int limit); + + // Get the directionality of the text. + public static native int ubidi_getDirection(final long pBiDi); + + // Get the length of the text. + public static native int ubidi_getLength(final long pBiDi); + + // Get the paragraph level of the text. + public static native byte ubidi_getParaLevel(final long pBiDi); + + // Get an array of levels for each character. + public static native byte[] ubidi_getLevels(long pBiDi); + + // Get the number of runs. + public static native int ubidi_countRuns(long pBiDi); + + // Get the BidiRuns + public static native BidiRun[] ubidi_getRuns(long pBidi); + + // This is a convenience function that does not use a UBiDi object + public static native int[] ubidi_reorderVisual(byte[] levels, int length); +} diff --git a/text/src/main/java/org/apache/harmony/text/internal/nls/Messages.java b/text/src/main/java/org/apache/harmony/text/internal/nls/Messages.java new file mode 100644 index 0000000..95a8ad6 --- /dev/null +++ b/text/src/main/java/org/apache/harmony/text/internal/nls/Messages.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * THE FILE HAS BEEN AUTOGENERATED BY MSGTOOL TOOL. + * All changes made to this file manually will be overwritten + * if this tool runs again. Better make changes in the template file. + */ + +// BEGIN android-note +// Redundant code has been removed and is now called from MsgHelp. +// END android-note + +package org.apache.harmony.text.internal.nls; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +// BEGIN android-changed +import org.apache.harmony.luni.util.MsgHelp; +// END android-changed + +/** + * This class retrieves strings from a resource bundle and returns them, + * formatting them with MessageFormat when required. + * <p> + * It is used by the system classes to provide national language support, by + * looking up messages in the <code> + * org.apache.harmony.text.internal.nls.messages + * </code> + * resource bundle. Note that if this file is not available, or an invalid key + * is looked up, or resource bundle support is not available, the key itself + * will be returned as the associated message. This means that the <em>KEY</em> + * should a reasonable human-readable (english) string. + * + */ +public class Messages { + + // BEGIN android-changed + private static final String sResource = + "org.apache.harmony.text.internal.nls.messages"; //$NON-NLS-1$ + // END android-changed + + /** + * Retrieves a message which has no arguments. + * + * @param msg + * String the key to look up. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg) { + // BEGIN android-changed + return MsgHelp.getString(sResource, msg); + // END android-changed + } + + /** + * Retrieves a message which takes 1 argument. + * + * @param msg + * String the key to look up. + * @param arg + * Object the object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg) { + return getString(msg, new Object[] { arg }); + } + + /** + * Retrieves a message which takes 1 integer argument. + * + * @param msg + * String the key to look up. + * @param arg + * int the integer to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, int arg) { + return getString(msg, new Object[] { Integer.toString(arg) }); + } + + /** + * Retrieves a message which takes 1 character argument. + * + * @param msg + * String the key to look up. + * @param arg + * char the character to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, char arg) { + return getString(msg, new Object[] { String.valueOf(arg) }); + } + + /** + * Retrieves a message which takes 2 arguments. + * + * @param msg + * String the key to look up. + * @param arg1 + * Object an object to insert in the formatted output. + * @param arg2 + * Object another object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg1, Object arg2) { + return getString(msg, new Object[] { arg1, arg2 }); + } + + /** + * Retrieves a message which takes several arguments. + * + * @param msg + * String the key to look up. + * @param args + * Object[] the objects to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object[] args) { + // BEGIN android-changed + return MsgHelp.getString(sResource, msg, args); + // END android-changed + } + + // BEGIN android-note + // Duplicate code was dropped in favor of using MsgHelp. + // END android-note +} diff --git a/text/src/main/java/org/apache/harmony/text/internal/nls/messages.properties b/text/src/main/java/org/apache/harmony/text/internal/nls/messages.properties new file mode 100644 index 0000000..22221a9 --- /dev/null +++ b/text/src/main/java/org/apache/harmony/text/internal/nls/messages.properties @@ -0,0 +1,46 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# messages for EN locale +text.00=min digits greater than max digits +text.01=min or max digits negative +text.02=Unknown attribute +text.03=Unknown pattern character - '{0}' +text.04=Unterminated quote +text.05=Invalid pattern char {0} in {1} +text.07=Unmatched braces in the pattern +text.06=Build rules empty +text.08=one of arguments is null +text.09=The deserialized date is invalid +text.0A=Invalid substring range +text.0B=Cannot add attributes to empty string +text.0C=cannot resolve subclasses +text.0E=Illegal date style: {0} +text.0F=Illegal time style: {0} +text.0D=Negative textStart value {0} +text.10=Negative embStart value {0} +text.11=Negative paragraph length {0} +text.12=Invalid ranges (start={0}, limit={1}, length={2}) +text.13=Invalid ranges (levels={0}, levelStart={1}, objects={2}, objectStart={3}, count={4}) +text.14=paragraph is null +text.19=Invalid argument number +text.15=Missing element format +text.16=Unknown element format +text.17=Unknown format +text.18=Not a valid {0}, subclass should override readResolve() +text.19=Unparseable date: {0} +text.1A=position is null + diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/AllTests.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/AllTests.java new file mode 100644 index 0000000..c2cfbb2 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/AllTests.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + + public static void main(String[] args) { + junit.textui.TestRunner.run(AllTests.suite()); + } + + public static Test suite() { + TestSuite suite = tests.TestSuiteFactory.createTestSuite( + "Suite org.apache.harmony.text.tests.java.text"); + //$JUnit-BEGIN$ + suite.addTestSuite(AnnotationTest.class); + suite.addTestSuite(AttributedCharacterIteratorAttributeTest.class); + suite.addTestSuite(AttributedCharacterIteratorTest.class); + suite.addTestSuite(AttributedStringTest.class); + suite.addTestSuite(BidiTest.class); + suite.addTestSuite(BreakIteratorTest.class); + suite.addTestSuite(ChoiceFormatTest.class); + suite.addTestSuite(CollationElementIteratorTest.class); + suite.addTestSuite(CollationKeyTest.class); + suite.addTestSuite(CollatorTest.class); + suite.addTestSuite(DataFormatFieldTest.class); + suite.addTestSuite(DateFormatSymbolsTest.class); + suite.addTestSuite(DateFormatTest.class); + suite.addTestSuite(DecimalFormatSymbolsTest.class); + suite.addTestSuite(DecimalFormatTest.class); + suite.addTestSuite(DecimalFormatTestICU.class); + suite.addTestSuite(FieldPositionTest.class); + suite.addTestSuite(FormatFieldTest.class); + suite.addTestSuite(FormatTest.class); + suite.addTestSuite(MessageFormatFieldTest.class); + suite.addTestSuite(MessageFormatTest.class); + suite.addTestSuite(NumberFormatFieldTest.class); + suite.addTestSuite(NumberFormatTest.class); + suite.addTestSuite(ParseExceptionTest.class); + suite.addTestSuite(ParsePositionTest.class); + suite.addTestSuite(RuleBasedCollatorTest.class); + suite.addTestSuite(SimpleDateFormatTest.class); + suite.addTestSuite(StringCharacterIteratorTest.class); + //$JUnit-END$ + return suite; + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/AnnotationTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/AnnotationTest.java new file mode 100644 index 0000000..e6af410 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/AnnotationTest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.text.Annotation; + +@TestTargetClass(Annotation.class) +public class AnnotationTest extends TestCase { + + /** + * @tests java.text.Annotation(Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Annotation", + args = {java.lang.Object.class} + ) + public void testAnnotation() { + assertNotNull(new Annotation(null)); + assertNotNull(new Annotation("value")); + } + + /** + * @tests java.text.Annotation.getValue() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getValue", + args = {} + ) + public void testGetValue() { + Annotation a = new Annotation(null); + assertNull(a.getValue()); + a = new Annotation("value"); + assertEquals("value", a.getValue()); + } + + /** + * @tests java.text.Annotation.toString() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "toString", + args = {} + ) + public void testToString() { + Annotation ant = new Annotation("HelloWorld"); + assertEquals("toString error.", + "java.text.Annotation[value=HelloWorld]", ant.toString()); + assertNotNull(new Annotation(null).toString()); + assertNotNull(new Annotation("value").toString()); + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/AttributedCharacterIteratorAttributeTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/AttributedCharacterIteratorAttributeTest.java new file mode 100644 index 0000000..ad4c9ca --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/AttributedCharacterIteratorAttributeTest.java @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.AttributedCharacterIterator; + + +@TestTargetClass(AttributedCharacterIterator.Attribute.class) +public class AttributedCharacterIteratorAttributeTest extends + junit.framework.TestCase { + + private class MockAttributedCharacterIteratorAttribute extends + AttributedCharacterIterator.Attribute { + + private static final long serialVersionUID = 1L; + + public MockAttributedCharacterIteratorAttribute(String name) { + super(name); + } + + @Override + public String getName() { + return super.getName(); + } + + @Override + public Object readResolve() throws InvalidObjectException { + return super.readResolve(); + } + } + + private class TestAttributedCharacterIteratorAttribute extends + AttributedCharacterIterator.Attribute { + private static final long serialVersionUID = -2917613373935785179L; + + public TestAttributedCharacterIteratorAttribute(String name) { + super(name); + } + } + + /** + * @tests java.text.AttributedCharacterIterator.Attribute#AttributedCharacterIterator.Attribute(java.lang.String) + * Test of method + * java.text.AttributedCharacterIterator.Attribute#AttributedCharacterIterator.Attribute(java.lang.String). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Attribute", + args = {java.lang.String.class} + ) + public void test_Constructor() { + try { + new MockAttributedCharacterIteratorAttribute("test"); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.AttributedCharacterIterator.Attribute#equals(java.lang.Object) + * Test of method + * java.text.AttributedCharacterIterator.Attribute#equals(java.lang.Object). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + try { + MockAttributedCharacterIteratorAttribute mac1 = new MockAttributedCharacterIteratorAttribute( + "test1"); + MockAttributedCharacterIteratorAttribute mac2 = new MockAttributedCharacterIteratorAttribute( + "test2"); + + assertFalse("Attributes are equal", mac2.equals(mac1)); + + TestAttributedCharacterIteratorAttribute mac3 = new TestAttributedCharacterIteratorAttribute( + "test1"); + + assertFalse("Attributes are equal", mac3.equals(mac1)); + + AttributedCharacterIterator.Attribute mac4 = mac1; + + assertTrue("Attributes are non-equal", mac4.equals(mac1)); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.AttributedCharacterIterator.Attribute#getName() Test of + * method java.text.AttributedCharacterIterator.Attribute#getName(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getName", + args = {} + ) + public void test_getName() { + try { + MockAttributedCharacterIteratorAttribute mac1 = new MockAttributedCharacterIteratorAttribute( + "test1"); + assertEquals("Incorrect attribute name", "test1", mac1.getName()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.AttributedCharacterIterator.Attribute#hashCode() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + try { + MockAttributedCharacterIteratorAttribute mac1 = new MockAttributedCharacterIteratorAttribute( + "test1"); + TestAttributedCharacterIteratorAttribute mac2 = new TestAttributedCharacterIteratorAttribute( + "test1"); + + assertTrue("The hash codes of same attributes are not equal", mac1 + .hashCode() != mac2.hashCode()); + + MockAttributedCharacterIteratorAttribute mac3 = new MockAttributedCharacterIteratorAttribute( + "test2"); + + assertTrue("The hash codes of different attributes are equal", mac1 + .hashCode() != mac3.hashCode()); + + AttributedCharacterIterator.Attribute mac4 = mac1; + + assertTrue("The hash codes of same attributes but different hierarchy classes are not equal", + mac1.hashCode() == mac4.hashCode()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.AttributedCharacterIterator.Attribute#readResolve() Test + * of method + * java.text.AttributedCharacterIterator.Attribute#readResolve(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "readResolve", + args = {} + ) + public void test_readResolve() { + MockAttributedCharacterIteratorAttribute mac1 = new MockAttributedCharacterIteratorAttribute( + "test"); + try { + mac1.readResolve(); + fail("InvalidObjectException has not been thrown"); + } catch (InvalidObjectException e) { + // expected + } + + ObjectOutputStream out = null; + ObjectInputStream in = null; + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + out = new ObjectOutputStream(bytes); + + AttributedCharacterIterator.Attribute attr1, attr2; + + attr1 = AttributedCharacterIterator.Attribute.LANGUAGE; + + out.writeObject(attr1); + + in = new ObjectInputStream(new ByteArrayInputStream(bytes + .toByteArray())); + + try { + attr2 = (AttributedCharacterIterator.Attribute) in.readObject(); + assertSame("resolved incorrectly", attr1, attr2); + } catch (IllegalArgumentException e) { + fail("Unexpected IllegalArgumentException: " + e); + } + + } catch (IOException e) { + fail("unexpected IOException" + e); + } catch (ClassNotFoundException e) { + fail("unexpected ClassNotFoundException" + e); + } finally { + try { + if (out != null) + out.close(); + if (in != null) + in.close(); + } catch (IOException e) { + } + } + } + + /** + * @tests java.text.AttributedCharacterIterator.Attribute#toString() Test of + * method java.text.AttributedCharacterIterator.Attribute#toString(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "toString", + args = {} + ) + public void test_toString() { + MockAttributedCharacterIteratorAttribute mac1 = new MockAttributedCharacterIteratorAttribute( + null); + assertEquals("Unexpected class representation string", mac1.toString(), + getClass().getName() + + "$MockAttributedCharacterIteratorAttribute(null)"); + TestAttributedCharacterIteratorAttribute mac2 = new TestAttributedCharacterIteratorAttribute( + "test1"); + assertEquals("Unexpected class representation string", mac2.toString(), + getClass().getName() + + "$TestAttributedCharacterIteratorAttribute(test1)"); + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/AttributedCharacterIteratorTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/AttributedCharacterIteratorTest.java new file mode 100644 index 0000000..8178760 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/AttributedCharacterIteratorTest.java @@ -0,0 +1,452 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; + +import java.text.AttributedCharacterIterator; +import java.text.AttributedString; +import java.text.CharacterIterator; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@TestTargetClass(AttributedCharacterIterator.class) +public class AttributedCharacterIteratorTest extends junit.framework.TestCase { + + AttributedCharacterIterator it; + String string = "test test"; + + /**z dxthf jgznm rff + * @tests java.text.AttributedCharacterIterator#current() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "current", + args = {} + ) + public void test_current() { + String test = "Test 23ring"; + AttributedString attrString = new AttributedString(test); + AttributedCharacterIterator it = attrString.getIterator(); + assertEquals("Wrong first", 'T', it.current()); + it.next(); + assertEquals("Wrong second", 'e', it.current()); + for (int i = 0; i < 9; i++) + it.next(); + assertEquals("Wrong last", 'g', it.current()); + it.next(); + assertTrue("Wrong final", it.current() == CharacterIterator.DONE); + + it = attrString.getIterator(null, 2, 8); + assertEquals("Wrong first2", 's', it.current()); + } + + /** + * @tests java.text.AttributedCharacterIterator#first() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "first", + args = {} + ) + public void test_first() { + String test = "Test 23ring"; + AttributedString attrString = new AttributedString(test); + AttributedCharacterIterator it = attrString.getIterator(); + assertEquals("Wrong first1", 'T', it.first()); + it = attrString.getIterator(null, 0, 3); + assertEquals("Wrong first2", 'T', it.first()); + it = attrString.getIterator(null, 2, 8); + assertEquals("Wrong first3", 's', it.first()); + it = attrString.getIterator(null, 11, 11); + assertTrue("Wrong first4", it.first() == CharacterIterator.DONE); + } + + /** + * @tests java.text.AttributedCharacterIterator#getBeginIndex() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getBeginIndex", + args = {} + ) + public void test_getBeginIndex() { + String test = "Test 23ring"; + AttributedString attrString = new AttributedString(test); + AttributedCharacterIterator it = attrString.getIterator(null, 2, 6); + assertEquals("Wrong begin index", 2, it.getBeginIndex()); + } + + /** + * @tests java.text.AttributedCharacterIterator#getEndIndex() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getEndIndex", + args = {} + ) + public void test_getEndIndex() { + String test = "Test 23ring"; + AttributedString attrString = new AttributedString(test); + AttributedCharacterIterator it = attrString.getIterator(null, 2, 6); + assertEquals("Wrong begin index", 6, it.getEndIndex()); + } + + /** + * @tests java.text.AttributedCharacterIterator#getIndex() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getIndex", + args = {} + ) + public void test_getIndex() { + String test = "Test 23ring"; + AttributedString attrString = new AttributedString(test); + AttributedCharacterIterator it = attrString.getIterator(); + assertEquals("Wrong first", 0, it.getIndex()); + it.next(); + assertEquals("Wrong second", 1, it.getIndex()); + for (int i = 0; i < 9; i++) + it.next(); + assertEquals("Wrong last", 10, it.getIndex()); + it.next(); + assertEquals("Wrong final", 11, it.getIndex()); + } + + /** + * @tests java.text.AttributedCharacterIterator#last() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "last", + args = {} + ) + public void test_last() { + String test = "Test 23ring"; + AttributedString attrString = new AttributedString(test); + AttributedCharacterIterator it = attrString.getIterator(); + assertEquals("Wrong last1", 'g', it.last()); + it = attrString.getIterator(null, 0, 3); + assertEquals("Wrong last2", 's', it.last()); + it = attrString.getIterator(null, 2, 8); + assertEquals("Wrong last3", 'r', it.last()); + it = attrString.getIterator(null, 0, 0); + assertTrue("Wrong last4", it.last() == CharacterIterator.DONE); + } + + /** + * @tests java.text.AttributedCharacterIterator#next() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "next", + args = {} + ) + public void test_next() { + String test = "Test 23ring"; + AttributedString attrString = new AttributedString(test); + AttributedCharacterIterator it = attrString.getIterator(); + assertEquals("Wrong first", 'e', it.next()); + for (int i = 0; i < 8; i++) + it.next(); + assertEquals("Wrong last", 'g', it.next()); + assertTrue("Wrong final", it.next() == CharacterIterator.DONE); + + it = attrString.getIterator(null, 2, 8); + assertEquals("Wrong first2", 't', it.next()); + } + + /** + * @tests java.text.AttributedCharacterIterator#previous() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "previous", + args = {} + ) + public void test_previous() { + String test = "Test 23ring"; + AttributedString attrString = new AttributedString(test); + AttributedCharacterIterator it = attrString.getIterator(); + it.setIndex(11); + assertEquals("Wrong first", 'g', it.previous()); + } + + /** + * @tests java.text.AttributedCharacterIterator#setIndex(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setIndex", + args = {int.class} + ) + public void test_setIndexI() { + String test = "Test 23ring"; + AttributedString attrString = new AttributedString(test); + AttributedCharacterIterator it = attrString.getIterator(); + it.setIndex(5); + assertEquals("Wrong first", '2', it.current()); + } + + /** + * @tests java.text.AttributedCharacterIterator#getRunLimit(java.text.AttributedCharacterIterator$Attribute) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getRunLimit", + args = {java.util.Set.class} + ) + public void test_getRunLimitLSet() { + AttributedString as = new AttributedString("test"); + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, "a", 2, + 3); + AttributedCharacterIterator it = as.getIterator(); + HashSet<AttributedCharacterIterator.Attribute> attr = + new HashSet<AttributedCharacterIterator.Attribute>(); + attr.add(AttributedCharacterIterator.Attribute.LANGUAGE); + assertEquals("non-null value limit", + 2, it.getRunLimit(attr)); + + as = new AttributedString("test"); + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, null, + 2, 3); + it = as.getIterator(); + assertEquals("null value limit", + 4, it.getRunLimit(attr)); + + attr.add(AttributedCharacterIterator.Attribute.READING); + assertEquals("null value limit", + 4, it.getRunLimit(attr)); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getAllAttributeKeys", + args = {} + ) + public void test_getAllAttributeKeys() { + AttributedString as = new AttributedString("test"); + AttributedCharacterIterator it = as.getIterator(); + Set<AttributedCharacterIterator.Attribute> emptyAttributes = + it.getAllAttributeKeys(); + assertTrue(emptyAttributes.isEmpty()); + + int attrCount = 10; + for(int i = 0 ; i < attrCount; i++) { + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, + "a"); + } + it = as.getIterator(); + Set<AttributedCharacterIterator.Attribute> attributes = + it.getAllAttributeKeys(); + for(AttributedCharacterIterator.Attribute attr:attributes) { + assertEquals(AttributedCharacterIterator.Attribute.LANGUAGE, attr); + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getAttribute", + args = {java.text.AttributedCharacterIterator.Attribute.class} + ) + public void test_getAttributeLAttributedCharacterIterator_Attribute() { + + Object attribute = + it.getAttribute(AttributedCharacterIterator.Attribute.LANGUAGE); + assertEquals("ENGLISH", attribute); + + attribute = + it.getAttribute(AttributedCharacterIterator.Attribute.READING); + assertEquals("READ", attribute); + + assertNull(it.getAttribute(AttributedCharacterIterator. + Attribute.INPUT_METHOD_SEGMENT)); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getAttributes", + args = {} + ) + public void test_getAttributes() { + Map<AttributedCharacterIterator.Attribute, Object> attributes = + it.getAttributes(); + assertEquals(2, attributes.size()); + assertEquals("ENGLISH", + attributes.get(AttributedCharacterIterator.Attribute.LANGUAGE)); + assertEquals("READ", + attributes.get(AttributedCharacterIterator.Attribute.READING)); + + AttributedString as = new AttributedString("test"); + assertTrue(as.getIterator().getAttributes().isEmpty()); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "getRunLimit", + args = {} + ) + public void test_getRunLimit() { + int limit = it.getRunLimit(); + assertEquals(string.length(), limit); + + AttributedString as = new AttributedString(""); + assertEquals(0, as.getIterator().getRunLimit()); + + as = new AttributedString(new AttributedString("test text"). + getIterator(), 2, 7); + + AttributedCharacterIterator it = as.getIterator(); + assertEquals(5, it.getRunLimit()); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getRunLimit", + args = {java.text.AttributedCharacterIterator.Attribute.class} + ) + public void test_getRunLimitLAttribute() { + AttributedString as = new AttributedString(""); + assertEquals(0, as.getIterator().getRunLimit( + AttributedCharacterIterator.Attribute.LANGUAGE)); + + as = new AttributedString("text"); + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, + "ENGLISH"); + + as.addAttribute(AttributedCharacterIterator.Attribute.READING, + "READ", 1, 3); + + assertEquals(4, as.getIterator().getRunLimit( + AttributedCharacterIterator.Attribute.LANGUAGE)); + + assertEquals(1, as.getIterator().getRunLimit( + AttributedCharacterIterator.Attribute.READING)); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "getRunStart", + args = {} + ) + public void test_getRunStart() { + assertEquals(0, it.getRunStart()); + + AttributedString as = new AttributedString(""); + assertEquals(0, as.getIterator().getRunStart()); + + as = new AttributedString(new AttributedString("test text"). + getIterator(), 2, 7); + + AttributedCharacterIterator it = as.getIterator(); + + assertEquals(0, it.getRunStart()); + + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, + "GERMAN", 1, 2); + as.addAttribute(AttributedCharacterIterator.Attribute.READING, + "READ", 1, 3); + assertEquals(0, as.getIterator().getRunStart()); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getRunStart", + args = {java.text.AttributedCharacterIterator.Attribute.class} + ) + public void test_getRunStartLAttribute() { + assertEquals(0, it.getRunStart( + AttributedCharacterIterator.Attribute.LANGUAGE)); + + AttributedString as = new AttributedString("test text"); + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, + "GERMAN", 2, 5); + as.addAttribute(AttributedCharacterIterator.Attribute.READING, + "READ", 2, 7); + + assertEquals(0, as.getIterator().getRunStart( + AttributedCharacterIterator.Attribute.LANGUAGE)); + assertEquals(0, as.getIterator().getRunStart( + AttributedCharacterIterator.Attribute.READING)); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getRunStart", + args = {java.util.Set.class} + ) + public void test_getRunStartLjava_util_Set() { + AttributedString as = new AttributedString("test"); + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, "a", 2, + 3); + AttributedCharacterIterator it = as.getIterator(); + HashSet<AttributedCharacterIterator.Attribute> attr = + new HashSet<AttributedCharacterIterator.Attribute>(); + attr.add(AttributedCharacterIterator.Attribute.LANGUAGE); + assertEquals(0, it.getRunStart(attr)); + + as = new AttributedString("test"); + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, + "ENGLISH",1, 3); + it = as.getIterator(); + assertEquals(0, it.getRunStart(attr)); + + attr.add(AttributedCharacterIterator.Attribute.READING); + assertEquals(0, it.getRunStart(attr)); + + + } + + protected void setUp() { + + AttributedString as = new AttributedString(string); + + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, + "GERMAN"); + as.addAttribute(AttributedCharacterIterator.Attribute.READING, + "READ"); + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, + "ENGLISH"); + + it = as.getIterator(); + } + + protected void tearDown() { + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/AttributedStringTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/AttributedStringTest.java new file mode 100644 index 0000000..86deba5 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/AttributedStringTest.java @@ -0,0 +1,609 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; + +import java.text.AttributedCharacterIterator; +import java.text.AttributedString; +import java.text.CharacterIterator; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.WeakHashMap; + +@TestTargetClass(AttributedString.class) +public class AttributedStringTest extends junit.framework.TestCase { + + static void assertEqualString (String msg, String expected, AttributedString attrString) { + AttributedCharacterIterator it = attrString.getIterator(); + StringBuffer buf = new StringBuffer(); + buf.append(it.first()); + char ch; + while ((ch = it.next()) != CharacterIterator.DONE) + buf.append(ch); + assertEquals(msg, expected, buf.toString()); + } + + /** + * @tests java.text.AttributedString#AttributedString(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "AttributedString", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() { + String test = "Test string"; + AttributedString attrString = new AttributedString(test); + assertEqualString("String must match!", test, attrString); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Functional Test", + method = "AttributedString", + args = {java.text.AttributedCharacterIterator.class} + ) + public void test_ConstructorLAttributedCharacterIterator_1() { + String testString = "Test string"; + AttributedString attrString = new AttributedString(testString); + AttributedCharacterIterator iter = attrString.getIterator(); + AttributedString attrString2 = new AttributedString(iter); + assertEqualString("String must match!", testString, attrString2); + } + + /** + * @tests java.text.AttributedString#AttributedString(AttributedCharacterIterator) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Regression for HARMONY-1354 (no functional test)", + method = "AttributedString", + args = {java.text.AttributedCharacterIterator.class} + ) + public void test_ConstructorLAttributedCharacterIterator() { + // Regression for HARMONY-1354 + assertNotNull(new AttributedString( + new testAttributedCharacterIterator())); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Functional Test", + method = "AttributedString", + args = {java.text.AttributedCharacterIterator.class, int.class, int.class} + ) + public void test_ConstructorLAttributedCharacterIterator_2() { + String testString = "Test string"; + AttributedString attrString = new AttributedString(testString); + AttributedCharacterIterator iter = attrString.getIterator(); + AttributedString attrString2 = new AttributedString(iter, 2, 7); + assertEqualString("String must match!", "st st", attrString2); + } + + /** + * @tests java.text.AttributedString#AttributedString(AttributedCharacterIterator, + * int, int) Test of method + * java.text.AttributedString#AttributedString(AttributedCharacterIterator, + * int, int). Case 1: Try to consruct AttributedString. Case 2: Try + * to consruct AttributedString using incorrect beginIndex. Case 3: + * Try to consruct AttributedString using incorrect endIndex. + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "No functional test.", + method = "AttributedString", + args = {java.text.AttributedCharacterIterator.class, int.class, int.class} + ) + public void test_ConstructorLAttributedCharacterIteratorII() { + // Regression for HARMONY-1355 + + // case 1: Try to consruct AttributedString. + try { + new AttributedString(new testAttributedCharacterIterator(), 0, 0); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + + // case 2: Try to consruct AttributedString using incorrect beginIndex. + try { + new AttributedString(new testAttributedCharacterIterator(), -1, 0); + fail("Expected IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + // expected + } + + // case 3: Try to consruct AttributedString using incorrect endIndex. + try { + new AttributedString(new testAttributedCharacterIterator(), 0, -1); + fail("Expected IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + // expected + } + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Functional Test", + method = "AttributedString", + args = {java.text.AttributedCharacterIterator.class, int.class, int.class, java.text.AttributedCharacterIterator.Attribute[].class} + ) + public void test_ConstructorLAttributedCharacterIterator_3() { + String testString = "Test string"; + AttributedString attrString = new AttributedString(testString); + AttributedCharacterIterator iter = attrString.getIterator(); + AttributedString attrString2; + + attrString2 = new AttributedString(iter, 2, 7, new AttributedCharacterIterator.Attribute[] {}); + assertEqualString("String must match!", "st st", attrString2); + + attrString2 = new AttributedString(iter, 2, 7, null); + assertEqualString("String must match!", "st st", attrString2); + } + + /** + * @tests java.text.AttributedString#AttributedString(AttributedCharacterIterator, + * int, int, AttributedCharacterIterator.Attribute[]) Test of method + * java.text.AttributedString#AttributedString(AttributedCharacterIterator, + * int, int, AttributedCharacterIterator.Attribute[]). Case 1: Try to + * consruct AttributedString. Case 2: Try to consruct + * AttributedString using incorrect beginIndex. Case 3: Try to + * consruct AttributedString using incorrect endIndex. Case 4: Try to + * consruct AttributedString using specified attributes. + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "No functional test.", + method = "AttributedString", + args = {java.text.AttributedCharacterIterator.class, int.class, int.class, java.text.AttributedCharacterIterator.Attribute[].class} + ) + public void test_ConstructorLAttributedCharacterIteratorII$Ljava_text_AttributedCharacterIterator$Attribute() { + // case 1: Try to consruct AttributedString. + try { + new AttributedString(new testAttributedCharacterIterator(), 0, 0, + null); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + + // case 2: Try to consruct AttributedString using incorrect beginIndex. + try { + new AttributedString(new testAttributedCharacterIterator(), -1, 0, + null); + fail("Expected IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + // expected + } + + // case 3: Try to consruct AttributedString using incorrect endIndex. + try { + new AttributedString(new testAttributedCharacterIterator(), 0, -1, + null); + fail("Expected IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + // expected + } + + // case 4: Try to consruct AttributedString using specified attributes. + try { + AttributedCharacterIterator.Attribute[] attributes = new AttributedCharacterIterator.Attribute[1]; + attributes[0] = new TestAttributedCharacterIteratorAttribute("test"); + new AttributedString(new testAttributedCharacterIterator(), 0, 0, + attributes); + } catch (IllegalArgumentException e) { + fail("Unexpected expected " + e.toString()); + } + } + + /** + * @tests java.text.AttributedString#AttributedString(AttributedCharacterIterator, + * int, int, Map<? extends AttributedCharacterIterator.Attribute,?>) + * Test of method + * java.text.AttributedString#AttributedString(AttributedCharacterIterator, + * int, int, Map<? extends + * AttributedCharacterIterator.Attribute,?>). Case 1: Try to + * construct AttributedString. Case 2: Try to construct + * AttributedString using 0-length text and not an empty Map + * attributes. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "AttributedString", + args = {java.lang.String.class, java.util.Map.class} + ) + public void test_ConstructorLjava_lang_StringLjava_util_Map() { + String test = "Test string"; + + // case 1: Try to construct AttributedString + try { + AttributedString attrString = new AttributedString( + test, + new WeakHashMap<AttributedCharacterIterator.Attribute, String>()); + AttributedCharacterIterator it = attrString.getIterator(); + StringBuffer buf = new StringBuffer(); + buf.append(it.first()); + char ch; + while ((ch = it.next()) != CharacterIterator.DONE) + buf.append(ch); + assertTrue("Wrong string: " + buf, buf.toString().equals(test)); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + + // case 2: Try to construct AttributedString using 0-length text and + // not an empty Map attributes. + try { + Map<AttributedCharacterIterator.Attribute, String> whm = new WeakHashMap<AttributedCharacterIterator.Attribute, String>(); + whm.put(new TestAttributedCharacterIteratorAttribute("test"), + "value"); + new AttributedString("", whm); + fail("Expected IllegalArgumentException was not thrown"); + } catch (Exception e) { + // expected + } + } + + private class TestAttributedCharacterIteratorAttribute extends + AttributedCharacterIterator.Attribute { + private static final long serialVersionUID = -2917613373935785179L; + + public TestAttributedCharacterIteratorAttribute(String name) { + super(name); + } + } + + private class testAttributedCharacterIterator implements + AttributedCharacterIterator { + public Set getAllAttributeKeys() { + return null; + } + + public Object getAttribute(AttributedCharacterIterator.Attribute p) { + return null; + } + + public Map getAttributes() { + return null; + } + + public int getRunLimit(Set p) { + return 0; + } + + public int getRunLimit(AttributedCharacterIterator.Attribute p) { + return 0; + } + + public int getRunLimit() { + return 0; + } + + public int getRunStart(Set p) { + return 0; + } + + public int getRunStart(AttributedCharacterIterator.Attribute p) { + return 0; + } + + public int getRunStart() { + return 0; + } + + public Object clone() { + return null; + } + + public int getIndex() { + return 0; + } + + public int getEndIndex() { + return 0; + } + + public int getBeginIndex() { + return 0; + } + + public char setIndex(int p) { + return 'a'; + } + + public char previous() { + return 'a'; + } + + public char next() { + return 'a'; + } + + public char current() { + return 'a'; + } + + public char last() { + return 'a'; + } + + public char first() { + return 'a'; + } + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "addAttribute", + args = {java.text.AttributedCharacterIterator.Attribute.class, java.lang.Object.class, int.class, int.class} + ) + public void test_addAttributeLjava_text_AttributedCharacterIterator$AttributeLjava_lang_ObjectII() { + AttributedString as = new AttributedString("test"); + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, "a", 2, + 3); + AttributedCharacterIterator it = as.getIterator(); + assertEquals("non-null value limit", 2, it + .getRunLimit(AttributedCharacterIterator.Attribute.LANGUAGE)); + + as = new AttributedString("test"); + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, null, + 2, 3); + it = as.getIterator(); + assertEquals("null value limit", 4, it + .getRunLimit(AttributedCharacterIterator.Attribute.LANGUAGE)); + + try { + as = new AttributedString("test"); + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, + null, -1, 3); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expected + } + + // regression for Harmony-1244 + as = new AttributedString("123", new WeakHashMap()); + try { + as.addAttribute(null, new TreeSet(), 0, 1); + fail("should throw NullPointerException"); + } catch (NullPointerException e) { + // expected + } + + try { + as.addAttribute(null, new TreeSet(), -1, 1); + fail("should throw NullPointerException"); + } catch (NullPointerException e) { + // expected + } + } + + /** + * @tests java.text.AttributedString.addAttribute(AttributedCharacterIterator, + * Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "addAttribute", + args = {java.text.AttributedCharacterIterator.Attribute.class, java.lang.Object.class} + ) + public void test_addAttributeLjava_text_AttributedCharacterIterator$AttributeLjava_lang_Object() { + // regression for Harmony-1244 + AttributedString as = new AttributedString("123", new WeakHashMap()); + + as.addAttribute(AttributedCharacterIterator.Attribute.LANGUAGE, "english"); + as.addAttribute(AttributedCharacterIterator.Attribute.INPUT_METHOD_SEGMENT, + "input method"); + as.addAttribute(AttributedCharacterIterator.Attribute.READING, "reading"); + + try { + as.addAttribute(null, new TreeSet()); + fail("should throw NullPointerException"); + } catch (NullPointerException e) { + // expected + } + try { + as.addAttribute(null, null); + fail("should throw NullPointerException"); + } catch (NullPointerException e) { + // expected + } + } + + /** + * @tests java.text.AttributedString#addAttributes(Map<? extends + * AttributedCharacterIterator.Attribute,?>, int, int) Tests of + * method java.text.AttributedString#addAttributes(Map<? extends + * AttributedCharacterIterator.Attribute,?>, int, int). Case 1: Try + * to add attributes to AttributesString. Case 2: Try to add + * null-attributes to AttributesString. Case 3: Try to add attributes + * to AttributesString using incorrect index. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "addAttributes", + args = {java.util.Map.class, int.class, int.class} + ) + public void test_addAttributesLjava_util_MapII() { + AttributedString as = new AttributedString("test"); + Map<AttributedCharacterIterator.Attribute, String> whm = new WeakHashMap<AttributedCharacterIterator.Attribute, String>(); + + // case 1: Try to add attributes to AttributesString. + try { + whm.put(new TestAttributedCharacterIteratorAttribute("test1"), + "value1"); + whm.put(new TestAttributedCharacterIteratorAttribute("test2"), + "value2"); + whm.put(new TestAttributedCharacterIteratorAttribute("test3"), + "value3"); + as.addAttributes(whm, 0, 3); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + + // case 2: Try to add null-attributes to AttributesString. + try { + as.addAttributes(null, 0, 3); + fail("Expected NullPointerException was not thrown"); + } catch (NullPointerException e) { + // expected + } + + // case 3: Try to add attributes to AttributesString using incorrect + // index. + try { + as.addAttributes(whm, 0, 0); + fail("Expected IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + // expected + } + } + + /** + * @tests java.text.AttributedString#getIterator() Test of method + * java.text.AttributedString#getIterator(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getIterator", + args = {} + ) + public void test_getIterator() { + String test = "Test string"; + try { + AttributedString attrString = new AttributedString(test); + AttributedCharacterIterator it = attrString.getIterator(); + assertEquals("Incorrect iteration on AttributedString", it.first(), + test.charAt(0)); + } catch (Exception e) { + fail("Unexpected exceptiption " + e.toString()); + } + } + + /** + * @tests java.text.AttributedString#getIterator(AttributedCharacterIterator.Attribute[]) + * Test of method + * java.text.AttributedString#getIterator(AttributedCharacterIterator.Attribute[]). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getIterator", + args = {java.text.AttributedCharacterIterator.Attribute[].class} + ) + public void test_getIterator$Ljava_text_AttributedCharacterIterator$Attribute() { + String test = "Test string"; + try { + Map<AttributedCharacterIterator.Attribute, String> hm = new HashMap<AttributedCharacterIterator.Attribute, String>(); + AttributedCharacterIterator.Attribute[] aci = new AttributedCharacterIterator.Attribute[3]; + aci[0] = new TestAttributedCharacterIteratorAttribute("att1"); + aci[1] = new TestAttributedCharacterIteratorAttribute("att2"); + aci[2] = new TestAttributedCharacterIteratorAttribute("att3"); + hm.put(aci[0], "value1"); + hm.put(aci[1], "value2"); + + AttributedString attrString = new AttributedString(test, hm); + AttributedCharacterIterator it = attrString.getIterator(aci); + + assertTrue("Incorrect iteration on AttributedString", it + .getAttribute(aci[0]).equals("value1")); + assertTrue("Incorrect iteration on AttributedString", it + .getAttribute(aci[1]).equals("value2")); + assertTrue("Incorrect iteration on AttributedString", it + .getAttribute(aci[2]) == null); + } catch (Exception e) { + fail("Unexpected exceptiption " + e.toString()); + } + } + + /** + * @tests java.text.AttributedString#getIterator(AttributedCharacterIterator.Attribute[], + * int, int) Test of method + * java.text.AttributedString#getIterator(AttributedCharacterIterator.Attribute[], + * int, int). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getIterator", + args = {java.text.AttributedCharacterIterator.Attribute[].class, int.class, int.class} + ) + public void test_getIterator$Ljava_text_AttributedCharacterIterator$AttributeII() { + String test = "Test string"; + try { + Map<AttributedCharacterIterator.Attribute, String> hm = new HashMap<AttributedCharacterIterator.Attribute, String>(); + AttributedCharacterIterator.Attribute[] aci = new AttributedCharacterIterator.Attribute[3]; + aci[0] = new TestAttributedCharacterIteratorAttribute("att1"); + aci[1] = new TestAttributedCharacterIteratorAttribute("att2"); + aci[2] = new TestAttributedCharacterIteratorAttribute("att3"); + hm.put(aci[0], "value1"); + hm.put(aci[1], "value2"); + + AttributedString attrString = new AttributedString(test); + attrString.addAttributes(hm, 2, 4); + AttributedCharacterIterator it = attrString.getIterator(aci, 1, 5); + + assertTrue("Incorrect iteration on AttributedString", it + .getAttribute(aci[0]) == null); + assertTrue("Incorrect iteration on AttributedString", it + .getAttribute(aci[1]) == null); + assertTrue("Incorrect iteration on AttributedString", it + .getAttribute(aci[2]) == null); + + it.next(); + + assertTrue("Incorrect iteration on AttributedString", it + .getAttribute(aci[0]).equals("value1")); + assertTrue("Incorrect iteration on AttributedString", it + .getAttribute(aci[1]).equals("value2")); + assertTrue("Incorrect iteration on AttributedString", it + .getAttribute(aci[2]) == null); + + try { + attrString.getIterator(aci, -1, 5); + fail("IllegalArgumentException is not thrown."); + } catch(IllegalArgumentException iae) { + //expected + } + + try { + attrString.getIterator(aci, 6, 5); + fail("IllegalArgumentException is not thrown."); + } catch(IllegalArgumentException iae) { + //expected + } + + try { + attrString.getIterator(aci, 3, 2); + fail("IllegalArgumentException is not thrown."); + } catch(IllegalArgumentException iae) { + //expected + } + } catch (Exception e) { + fail("Unexpected exceptiption " + e.toString()); + } + + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/BidiTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/BidiTest.java new file mode 100644 index 0000000..6562019 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/BidiTest.java @@ -0,0 +1,2064 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.AndroidOnly; +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargets; + +import junit.framework.TestCase; + +import java.text.AttributedString; +import java.text.Bidi; +import java.util.Arrays; + +@TestTargetClass(Bidi.class) +public class BidiTest extends TestCase { + + Bidi bd; + + public static void assertRunArrayEquals(int[][] expected, Bidi bidi) { + assertEquals("different length", expected.length, bidi.getRunCount()); + + FORRUN: for (int i = 0; i < bidi.getRunCount(); i++) { + int[] butWas = new int[] { bidi.getRunStart(i), + bidi.getRunLimit(i), bidi.getRunLevel(i) }; + + for (int j = 0; j < expected.length; j++) { + if (expected[j][0] == butWas[0] && expected[j][1] == butWas[1] + && expected[j][2] == butWas[2]) { + continue FORRUN; + } + } + fail("expected [" + i + "] " + " start: " + butWas[0] + " limit: " + + butWas[1] + " level: " + butWas[2]); + } + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify positive case for Bidi(AttributedCharacterIterator paragraph).", + method = "Bidi", + args = {char[].class, int.class, byte[].class, int.class, int.class, int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify positive case for Bidi(AttributedCharacterIterator paragraph).", + method = "Bidi", + args = {java.text.AttributedCharacterIterator.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "Bidi", + args = {String.class, int.class} + ) + }) + public void testNullPointerConstructor() { + try { + bd = new Bidi(null, Bidi.DIRECTION_RIGHT_TO_LEFT); + fail("should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + bd = new Bidi(null, 0, new byte[] { 0 }, 0, 0, + Bidi.DIRECTION_RIGHT_TO_LEFT); + fail("should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + bd = new Bidi(null); + fail("should throw IAE"); + } catch (IllegalArgumentException e) { + } + + bd = new Bidi("a".toCharArray(), 0, null, 0, 1, + Bidi.DIRECTION_RIGHT_TO_LEFT); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "Bidi", + args = {char[].class, int.class, byte[].class, int.class, int.class, int.class} + ) + public void testBadLength() { + try { + bd = new Bidi("1".toCharArray(), 0, new byte[] { 0 }, 0, 20, + Bidi.DIRECTION_RIGHT_TO_LEFT); + fail("should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + bd = new Bidi("1234567".toCharArray(), 0, new byte[] { 0 }, 0, 4, + Bidi.DIRECTION_RIGHT_TO_LEFT); + fail("should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + bd = new Bidi("1234567".toCharArray(), 4, new byte[] { 0, 1, 2, 3, + 4 }, 0, 5, Bidi.DIRECTION_RIGHT_TO_LEFT); + fail("should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + bd = new Bidi("1234567".toCharArray(), 0, new byte[] { 0, 1, 2, 3, + 4 }, 4, 5, Bidi.DIRECTION_RIGHT_TO_LEFT); + fail("should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + // regression for HARMONY-1031 + try { + bd = new Bidi(new char[] { 't', 't', 't' }, -1, + new byte[] { 2, 2 }, 1, 1, 1); + fail("should be IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + bd = new Bidi(new char[] { 't', 't', 't' }, 1, new byte[] { 2, 2 }, + -1, 1, 1); + fail("should be IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + bd = new Bidi(new char[] { 't', 't', 't' }, 1, new byte[] { 2, 2 }, + 1, -1, 1); + fail("should be IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + bd = new Bidi(new char[] {}, 5, new byte[] { 2, 2, 2, 2, 2, 2 }, 8, + Integer.MAX_VALUE, 5); + fail("should be IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + bd = new Bidi(null, 5, null, 8, Integer.MAX_VALUE, 5); + fail("should be IllegalArgumentException."); + } catch (IllegalArgumentException e) { + // expected + } + + bd = new Bidi(new char[] { 'o' }, 0, new byte[] { 2, 2 }, 2, 0, 2); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "Bidi", + args = {String.class, int.class} + ) + }) + public void testEmptyParagraph() { + bd = new Bidi("", Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(0, bd.getLength()); + assertEquals(0, bd.getLevelAt(0)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 0, 0 } }, bd); + assertTrue(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi("", Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(0, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 0, 1 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertTrue(bd.isRightToLeft()); + + bd = new Bidi("", Bidi.DIRECTION_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(0, bd.getLength()); + assertEquals(0, bd.getLevelAt(0)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 0, 0 }, }, bd); + assertTrue(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi("", Bidi.DIRECTION_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(0, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 0, 1 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertTrue(bd.isRightToLeft()); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ) + }) + public void testSpaceParagraph() { + bd = new Bidi(" ", Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(1, bd.getLength()); + assertEquals(0, bd.getLevelAt(0)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 0 }, }, bd); + assertTrue(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi(" ", Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(1, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertTrue(bd.isRightToLeft()); + + bd = new Bidi(" ", Bidi.DIRECTION_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(1, bd.getLength()); + assertEquals(0, bd.getLevelAt(0)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 0 }, }, bd); + assertTrue(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi(" ", Bidi.DIRECTION_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(1, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertTrue(bd.isRightToLeft()); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ) + }) + public void testSimpleParagraph() { + bd = new Bidi("t", Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(1, bd.getLength()); + assertEquals(0, bd.getLevelAt(0)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 0 }, }, bd); + assertTrue(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi("t", Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(1, bd.getLength()); + assertEquals(0, bd.getLevelAt(0)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 0 }, }, bd); + assertTrue(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi("t", Bidi.DIRECTION_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(1, bd.getLength()); + assertEquals(0, bd.getLevelAt(0)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 0 }, }, bd); + assertTrue(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + } + + /** + * @tests java.text.Bidi#toString() Test of method java.text.Bidi#toString() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "toString", + args = {} + ) + public void testToString() { + try { + bd = new Bidi("bidi", 173); + assertNotNull("Bidi representation is null", bd.toString()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify that these methods can return all possible flags.", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify that these methods can return all possible flags.", + method = "getLength", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify that these methods can return all possible flags.", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify that these methods can return all possible flags.", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify that these methods can return all possible flags.", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify that these methods can return all possible flags.", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify that these methods can return all possible flags.", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify that these methods can return all possible flags.", + method = "Bidi", + args = {java.lang.String.class, int.class} + ) + }) + public void testBadFlags() { + bd = new Bidi("", 173); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(0, bd.getLength()); + assertEquals(0, bd.getLevelAt(0)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 0, 0 }, }, bd); + assertTrue(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Verifies IllegalArgumentException.", + method = "Bidi", + args = {char[].class, int.class, byte[].class, int.class, int.class, int.class} + ) + public void testBadEmbeddings() { + try { + bd = new Bidi("".toCharArray(), 0, new byte[] {}, 0, 1, + Bidi.DIRECTION_RIGHT_TO_LEFT); + fail("should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ) + }) + public void testOverrideEmbeddings() { + bd = new Bidi(new char[] { 's', 's', 's' }, 0, new byte[] { (byte) -7, + (byte) -2, (byte) -3 }, 0, 3, + Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(3, bd.getLength()); + assertEquals(7, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(3, bd.getLevelAt(2)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(3, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 7 }, { 1, 2, 2 }, + { 2, 3, 3 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi(new char[] { 's', 's', 's' }, 0, new byte[] { (byte) -1, + (byte) -2, (byte) -3 }, 0, 3, + Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(3, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(3, bd.getLevelAt(2)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(3, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, { 1, 2, 2 }, + { 2, 3, 3 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi(new char[] { 's', 's', 's' }, 0, new byte[] { (byte) -1, + (byte) -2, (byte) -3 }, 0, 3, Bidi.DIRECTION_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(3, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(3, bd.getLevelAt(2)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(3, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, { 1, 2, 2 }, + { 2, 3, 3 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi(new char[] { 's', 's', 's' }, 0, new byte[] { (byte) -1, + (byte) -2, (byte) -3 }, 0, 3, Bidi.DIRECTION_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(3, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(3, bd.getLevelAt(2)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(3, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, { 1, 2, 2 }, + { 2, 3, 3 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ) + }) + public void testDefaultEmbeddings() { + bd = new Bidi(new char[] { 's', 's', 's' }, 0, new byte[] { (byte) 0, + (byte) 0, (byte) 0 }, 0, 3, Bidi.DIRECTION_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(3, bd.getLength()); + assertEquals(2, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(2, bd.getLevelAt(2)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 3, 2 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ) + }) + public void testRelativeEmbeddings() { + bd = new Bidi(new char[] { 's', 's', 's' }, 0, new byte[] { (byte) 1, + (byte) 2, (byte) 3 }, 0, 3, Bidi.DIRECTION_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(3, bd.getLength()); + assertEquals(2, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(4, bd.getLevelAt(2)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(2, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 2, 2 }, { 2, 3, 4 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "Bidi", + args = {String.class, int.class} + ) + }) + public void testSimpleHebrewParagraph() { + bd = new Bidi("\u05D0", Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(1, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertTrue(bd.isRightToLeft()); + + bd = new Bidi("\u05D0", Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(1, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertTrue(bd.isRightToLeft()); + + bd = new Bidi("\u05D0", Bidi.DIRECTION_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(1, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertFalse(bd.isMixed()); + assertTrue(bd.isRightToLeft()); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "Bidi", + args = {String.class, int.class} + ) + }) + public void testSimpleBidiParagraph_1() { + bd = new Bidi("\u05D0a", Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(2, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(2, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, { 1, 2, 2 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi("\u05D0a", Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(2, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(2, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, { 1, 2, 2 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi("\u05D0a", Bidi.DIRECTION_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(2, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(0, bd.getLevelAt(1)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(2, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, { 1, 2, 0 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi("\u05D0a", Bidi.DIRECTION_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(2, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(2, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, { 1, 2, 2 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "Bidi", + args = {String.class, int.class} + ) + }) + public void testSimpleBidiParagraph_2() { + bd = new Bidi("a\u05D0", Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(2, bd.getLength()); + assertEquals(0, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(2, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 0 }, { 1, 2, 1 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi("a\u05D0", Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(2, bd.getLength()); + assertEquals(0, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(2, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 0 }, { 1, 2, 1 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi("a\u05D0", Bidi.DIRECTION_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(2, bd.getLength()); + assertEquals(0, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(2, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 0 }, { 1, 2, 1 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi("a\u05D0", Bidi.DIRECTION_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(2, bd.getLength()); + assertEquals(2, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(2, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 2 }, { 1, 2, 1 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + } + + /* + * spec reads: public static final int DIRECTION_RIGHT_TO_LEFT Constant + * indicating base direction is right-to-left. according to that, the method + * baseIsLeftToRight() here should return false. however, RI doesn't act so. + */ + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "Bidi", + args = {String.class, int.class} + ) + }) + public void testRIBug_1() { + bd = new Bidi("t", Bidi.DIRECTION_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + // the base level it the essential cause + assertEquals(1, bd.getBaseLevel()); + assertEquals(1, bd.getLength()); + assertEquals(2, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 2 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "Bidi", + args = {String.class, int.class} + ) + }) + // this is essentially the same bug as Bug_1 + public void testRIBug_2() { + bd = new Bidi("\u05D0", Bidi.DIRECTION_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(1, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(1, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "Bidi", + args = {String.class, int.class} + ) + }) + public void testComplicatedBidi() { + bd = new Bidi("a\u05D0a\"a\u05D0\"\u05D0a", + Bidi.DIRECTION_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(9, bd.getLength()); + assertEquals(2, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1)); + assertEquals(2, bd.getLevelAt(2)); + assertEquals(2, bd.getLevelAt(3)); + assertEquals(2, bd.getLevelAt(4)); + assertEquals(1, bd.getLevelAt(5)); + assertEquals(1, bd.getLevelAt(6)); + assertEquals(1, bd.getLevelAt(7)); + assertEquals(2, bd.getLevelAt(8)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(5, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 2 }, { 1, 2, 1 }, + { 2, 5, 2 }, { 5, 8, 1 }, { 8, 9, 2 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "Bidi", + args = {char[].class, int.class, byte[].class, int.class, int.class, int.class} + ) + }) + public void testComplicatedOverrideBidi() { + bd = new Bidi("a\u05D0a\"a\u05D0\"\u05D0a".toCharArray(), 0, + new byte[] { 0, 0, 0, -3, -3, 2, 2, 0, 3 }, 0, 9, + Bidi.DIRECTION_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(9, bd.getLength()); + assertEquals(2, bd.getLevelAt(0)); + assertEquals(1, bd.getLevelAt(1)); + assertEquals(2, bd.getLevelAt(2)); + assertEquals(3, bd.getLevelAt(3)); + assertEquals(3, bd.getLevelAt(4)); + assertEquals(3, bd.getLevelAt(5)); + assertEquals(2, bd.getLevelAt(6)); + assertEquals(1, bd.getLevelAt(7)); + assertEquals(4, bd.getLevelAt(8)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(7, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 2 }, { 1, 2, 1 }, + { 2, 3, 2 }, { 3, 6, 3 }, { 6, 7, 2 }, { 7, 8, 1 }, + { 8, 9, 4 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "requiresBidi", + args = {char[].class, int.class, int.class} + ) + public void testRequiresBidi() { + try { + Bidi.requiresBidi(null, 0, 0); + fail("should throw NullPointerException"); + } catch (NullPointerException e) { + // expected + } + + try { + assertFalse(Bidi.requiresBidi(null, 0, 1)); + fail("should throw NPE"); + } catch (NullPointerException e) { + // expected + } + try { + assertFalse(Bidi.requiresBidi("".toCharArray(), 0, 1)); + fail("should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + try { + assertFalse(Bidi.requiresBidi("aaa".toCharArray(), -1, 1)); + fail("should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + try { + assertFalse(Bidi.requiresBidi("aaa".toCharArray(), 1, -1)); + fail("should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + try { + assertFalse(Bidi.requiresBidi("\u05D0".toCharArray(), 1, -1)); + fail("should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + try { + assertFalse(Bidi.requiresBidi("aaa".toCharArray(), 1, 0)); + fail("should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + try { + assertFalse(Bidi.requiresBidi("aaa".toCharArray(), 7, 7)); + fail("should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + try { + assertFalse(Bidi.requiresBidi("aaa".toCharArray(), 1, + Integer.MAX_VALUE)); + fail("should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + try { + assertFalse(Bidi.requiresBidi("aaa".toCharArray(), + Integer.MAX_VALUE, 1)); + fail("should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + + assertFalse(Bidi.requiresBidi("".toCharArray(), 0, 0)); + assertFalse(Bidi.requiresBidi("aaa".toCharArray(), 1, 1)); + assertFalse(Bidi.requiresBidi("aaa".toCharArray(), 0, 2)); + assertFalse(Bidi.requiresBidi("\u05D0".toCharArray(), 1, 1)); + assertTrue(Bidi.requiresBidi("\u05D0".toCharArray(), 0, 1)); + assertFalse(Bidi.requiresBidi("aa\u05D0a".toCharArray(), 0, 2)); + assertTrue(Bidi.requiresBidi("aa\u05D0a".toCharArray(), 1, 3)); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "Bidi", + args = {char[].class, int.class, byte[].class, int.class, int.class, int.class} + ) + }) + public void testHebrewOverrideEmbeddings() { + bd = new Bidi(new char[] { '\u05D0', '\u05D0', '\u05D0' }, 0, + new byte[] { (byte) -1, (byte) -2, (byte) -3 }, 0, 3, + Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(3, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(3, bd.getLevelAt(2)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(3, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, { 1, 2, 2 }, + { 2, 3, 3 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi(new char[] { '\u05D0', '\u05D0', '\u05D0' }, 0, + new byte[] { (byte) -1, (byte) -2, (byte) -3 }, 0, 3, + Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(3, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(3, bd.getLevelAt(2)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(3, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, { 1, 2, 2 }, + { 2, 3, 3 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi(new char[] { '\u05D0', '\u05D0', '\u05D0' }, 0, + new byte[] { (byte) -1, (byte) -2, (byte) -3 }, 0, 3, + Bidi.DIRECTION_LEFT_TO_RIGHT); + assertTrue(bd.baseIsLeftToRight()); + assertEquals(0, bd.getBaseLevel()); + assertEquals(3, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(3, bd.getLevelAt(2)); + assertEquals(0, bd.getLevelAt(1000)); + assertEquals(3, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, { 1, 2, 2 }, + { 2, 3, 3 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + + bd = new Bidi(new char[] { '\u05D0', '\u05D0', '\u05D0' }, 0, + new byte[] { (byte) -1, (byte) -2, (byte) -3 }, 0, 3, + Bidi.DIRECTION_RIGHT_TO_LEFT); + assertFalse(bd.baseIsLeftToRight()); + assertEquals(1, bd.getBaseLevel()); + assertEquals(3, bd.getLength()); + assertEquals(1, bd.getLevelAt(0)); + assertEquals(2, bd.getLevelAt(1)); + assertEquals(3, bd.getLevelAt(2)); + assertEquals(1, bd.getLevelAt(1000)); + assertEquals(3, bd.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 1 }, { 1, 2, 2 }, + { 2, 3, 3 }, }, bd); + assertFalse(bd.isLeftToRight()); + assertTrue(bd.isMixed()); + assertFalse(bd.isRightToLeft()); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "createLineBidi", + args = {int.class, int.class} + ) + }) + public void testCreateLineBidi() { + bd = new Bidi("a\u05D0a\na\u05D0\"\u05D0a".toCharArray(), 0, + new byte[] { 0, 0, 0, -3, -3, 2, 2, 0, 3 }, 0, 9, + Bidi.DIRECTION_RIGHT_TO_LEFT); + Bidi line = bd.createLineBidi(2, 7); + assertFalse(line.baseIsLeftToRight()); + assertEquals(1, line.getBaseLevel()); + assertEquals(5, line.getLength()); + assertEquals(2, line.getLevelAt(0)); + assertEquals(1, line.getLevelAt(1)); + assertEquals(3, line.getLevelAt(2)); + assertEquals(3, line.getLevelAt(3)); + assertEquals(2, line.getLevelAt(4)); + assertEquals(1, line.getLevelAt(1000)); + assertEquals(4, line.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 1, 2 }, { 1, 2, 1 }, + { 2, 4, 3 }, { 4, 5, 2 }, }, line); + assertFalse(line.isLeftToRight()); + assertTrue(line.isMixed()); + assertFalse(line.isRightToLeft()); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Verifies IllegalArgumentException.", + method = "createLineBidi", + args = {int.class, int.class} + ) + public void testCreateLineBidiInvalid() { + // regression for HARMONY-1050 + Bidi bidi = new Bidi("str", 1); + try { + bidi.createLineBidi(-1, 1); + fail("Expected IAE"); + } catch (IllegalArgumentException e) { + // Expected + } + + try { + bidi.createLineBidi(1, -1); + fail("Expected IAE"); + } catch (IllegalArgumentException e) { + // Expected + } + + try { + bidi.createLineBidi(-1, -1); + fail("Expected IAE"); + } catch (IllegalArgumentException e) { + // Expected + } + + try { + bidi.createLineBidi(2, 1); + fail("Expected IAE"); + } catch (IllegalArgumentException e) { + // Expected + } + +// Outsourced to _AndroidFailure: +// bidi.createLineBidi(2, 2); + + try { + bidi.createLineBidi(2, 4); + fail("Expected IAE"); + } catch (IllegalArgumentException e) { + // Expected + } + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + method = "createLineBidi", + args = {int.class, int.class} + ) + @KnownFailure("Is this a failure or just a different behaviour of ICU?") + public void testCreateLineBidi_AndroidFailure() { + Bidi bidi = new Bidi("str", 1); + bidi.createLineBidi(2, 2); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "baseIsLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLevelAt", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getRunCount", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isRightToLeft", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isLeftToRight", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "isMixed", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getBaseLevel", + args = {} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "getLength", + args = {} + ) + }) + @AndroidOnly("ICU treats new lines different from RI.") + public void testIncompatibleLineAlgorithm() { + // ICU treat a new line as in the same run, however RI does not + bd = new Bidi("aaaaa".toCharArray(), 0, + new byte[] { -2, -1, -3, -3, -2 }, 0, 5, + Bidi.DIRECTION_RIGHT_TO_LEFT); + Bidi line = bd.createLineBidi(1, 4); + assertFalse(line.baseIsLeftToRight()); + assertEquals(1, line.getBaseLevel()); + assertEquals(3, line.getLength()); + assertEquals(1, line.getLevelAt(0)); + assertEquals(1, line.getLevelAt(1)); + assertEquals(1, line.getLevelAt(2)); + assertEquals(1, line.getLevelAt(1000)); + assertEquals(1, line.getRunCount()); + assertRunArrayEquals(new int[][] { { 0, 3, 1 }, }, line); + assertFalse(line.isLeftToRight()); + assertFalse(line.isMixed()); + assertTrue(line.isRightToLeft()); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "reorderVisually", + args = {byte[].class, int.class, java.lang.Object[].class, int.class, int.class} + ) + public void testReorderVisually() { + String[] init = new String[] { "a", "b", "c", "d" }; + String[] s = new String[4]; + + System.arraycopy(init, 0, s, 0, s.length); + Bidi.reorderVisually(new byte[] { 2, 1, 3, 0 }, 0, s, 0, 4); + assertEquals("[c, b, a, d]", Arrays.asList(s).toString()); + + System.arraycopy(init, 0, s, 0, s.length); + Bidi.reorderVisually(new byte[] { 1, 3 }, 0, s, 1, 2); + assertEquals("[a, c, b, d]", Arrays.asList(s).toString()); + + System.arraycopy(init, 0, s, 0, s.length); + Bidi.reorderVisually(new byte[] { 2, 1, 3, 0 }, 1, s, 1, 2); + assertEquals("[a, c, b, d]", Arrays.asList(s).toString()); + + System.arraycopy(init, 0, s, 0, s.length); + Bidi.reorderVisually(new byte[] { 2, 1, 2, 1 }, 1, s, 0, 3); + assertEquals("[c, b, a, d]", Arrays.asList(s).toString()); + + System.arraycopy(init, 0, s, 0, s.length); + Bidi.reorderVisually(new byte[] { 2, 1, 0, 1 }, 1, s, 0, 3); + assertEquals("[a, b, c, d]", Arrays.asList(s).toString()); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Verifies exceptions.", + method = "reorderVisually", + args = {byte[].class, int.class, java.lang.Object[].class, int.class, int.class} + ) + public void testBadReorderVisually() { + String[] s = new String[] { "a", "b", "c", "d" }; + + try { + Bidi.reorderVisually(new byte[] { 2, 1, 3, 0 }, 0, s, 0, 5); + fail("should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + Bidi.reorderVisually(new byte[] { 2, 1, 3, 0 }, 0, s, -1, 1); + fail("should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + Bidi.reorderVisually(new byte[] { 2, 1, 3, 0 }, -1, s, 0, 1); + fail("should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + Bidi.reorderVisually(null, 0, s, 0, 1); + fail("should throw NPE"); + } catch (NullPointerException e) { + // expected + } + + try { + Bidi.reorderVisually(new byte[] { 2, 1, 3, 0 }, 0, null, 0, 1); + fail("should throw NPE"); + } catch (NullPointerException e) { + // expected + } + + try { + Bidi.reorderVisually(new byte[] { 2, 1, 3, 0 }, 1, s, 0, -1); + fail("should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Regression test.", + method = "getRunLimit", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Regression test.", + method = "getRunStart", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Regression test.", + method = "getRunCount", + args = {} + ) + }) + public void testGetRuns() { + // Regression test for Harmony-1028 + + String LTR = "\u0061\u0062"; + String RTL = "\u05DC\u05DD"; + String newLine = "\n"; + String defText = LTR + newLine + RTL + LTR + RTL; + + int[][] expectedRuns = { { 0, 3 }, { 3, 5 }, { 5, 7 }, { 7, 9 }, }; + + Bidi bi = new Bidi(defText, 0); + final int count = bi.getRunCount(); + for (int i = 0; i < count; i++) { + assertEquals(expectedRuns[i][0], bi.getRunStart(i)); + assertEquals(expectedRuns[i][1], bi.getRunLimit(i)); + } + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify any int value between 0 and getRunCount().", + method = "getRunLimit", + args = {int.class} + ) + public void testGetRunLimit() { + bd = new Bidi("text", Bidi.DIRECTION_LEFT_TO_RIGHT); + try { + assertTrue(4 == bd.getRunLimit(-1)); + } catch (Exception e) { + fail("Unexpected exception: " + e); + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getRunLevel", + args = {int.class} + ) + public void testGetRunLevelLInt() { + bd = new Bidi("text", Bidi.DIRECTION_LEFT_TO_RIGHT); + try { + assertEquals(0, bd.getRunLevel(0)); + assertEquals(0, bd.getRunLevel(bd.getRunCount())); + } catch (Exception e) { + fail("Unexpected exception: " + e); + } + + bd = new Bidi("text", Bidi.DIRECTION_RIGHT_TO_LEFT); + try { + assertEquals(2, bd.getRunLevel(0)); + } catch (Exception e) { + fail("Unexpected exception: " + e); + } + + bd = new Bidi("text", Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT); + try { + assertEquals(0, bd.getRunLevel(0)); + } catch (Exception e) { + fail("Unexpected exception: " + e); + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getRunStart", + args = {int.class} + ) + public void testGetRunStart() { + bd = new Bidi(new char[] { 's', 's', 's' }, 0, new byte[] { (byte) -7, + (byte) -2, (byte) 3 }, 0, 3, + Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); + assertEquals(0, bd.getRunStart(0)); + assertEquals(1, bd.getRunStart(1)); + assertEquals(2, bd.getRunStart(2)); + + String LTR = "\u0061\u0062"; + String RTL = "\u05DC\u05DD"; + String newLine = "\n"; + String defText = LTR + newLine + RTL + LTR + RTL; + + int[][] expectedRuns = { { 0, 3 }, { 3, 5 }, { 5, 7 }, { 7, 9 }, }; + + Bidi bi = new Bidi(defText, 0); + + final int count = bi.getRunCount(); + for (int i = 0; i < count; i++) { + assertEquals(expectedRuns[i][0], bi.getRunStart(i)); + } + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "Bidi", + args = {java.text.AttributedCharacterIterator.class} + ) + public void testBidiConstructor_Iterator() { + AttributedString paragraph = new AttributedString("text"); + bd = new Bidi(paragraph.getIterator()); + try { + assertTrue(4 == bd.getRunLimit(1)); + } catch (Exception e) { + fail("Unexpected exception: " + e); + } + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/BreakIteratorTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/BreakIteratorTest.java new file mode 100644 index 0000000..f3b0b31 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/BreakIteratorTest.java @@ -0,0 +1,803 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.text.BreakIterator; +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; +import java.util.Locale; + +@TestTargetClass(BreakIterator.class) +public class BreakIteratorTest extends TestCase { + + private static final String TEXT = "a\u0308abc def, gh-12i?jkl.mno?"; + + BreakIterator iterator; + + /* + * @see TestCase#setUp() + */ + protected void setUp() throws Exception { + super.setUp(); + iterator = BreakIterator.getCharacterInstance(Locale.US); + } + @TestTargetNew( + level = TestLevel.TODO, + notes = "Verifies constant.", + method = "!Constants", + args = {} + ) + public void testConsts() { + assertEquals(-1, BreakIterator.DONE); + } + @TestTargets({ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCharacterInstance", + args = {} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCharacterInstance", + args = {java.util.Locale.class} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getWordInstance", + args = {} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getLineInstance", + args = {} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getSentenceInstance", + args = {} + ) + }) + public void testCache() { + BreakIterator newOne = BreakIterator.getCharacterInstance(Locale.US); + assertNotSame(newOne, iterator); + assertEquals(newOne, iterator); + + newOne = BreakIterator.getCharacterInstance(); + assertEquals(newOne, iterator); + + newOne = BreakIterator.getCharacterInstance(Locale.CHINA); + assertEquals(newOne, iterator); + + BreakIterator wordIterator = BreakIterator.getWordInstance(); + assertFalse(wordIterator.equals(iterator)); + + BreakIterator lineIterator = BreakIterator.getLineInstance(); + assertFalse(lineIterator.equals(iterator)); + + BreakIterator senteIterator = BreakIterator.getSentenceInstance(); + assertFalse(senteIterator.equals(iterator)); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void testClone() { + BreakIterator cloned = (BreakIterator) iterator.clone(); + assertNotSame(cloned, iterator); + assertEquals(cloned, iterator); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "current", + args = {} + ) + public void testCurrent() { + assertEquals(0, iterator.current()); + iterator.setText(TEXT); + assertEquals(iterator.first(), iterator.current()); + } + + /** + * @tests java.text.BreakIterator#BreakIterator() Test of method + * java.text.BreakIterator#BreakIterator(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "BreakIterator", + args = {} + ) + public void testConstructor() { + try { + new MockBreakIterator(); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "first", + args = {} + ) + public void testFirst() { + assertEquals(0, iterator.first()); + iterator.setText(TEXT); + assertEquals(0, iterator.first()); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "following", + args = {int.class} + ) + public void testFollowing() { + try { + iterator.following(1); + fail("should throw illegal argument exception"); + } catch (IllegalArgumentException e) { + } + iterator.setText(TEXT); + assertEquals(2, iterator.following(1)); + try { + assertEquals(0, iterator.following(-1)); + fail("should throw illegal argument exception"); + } catch (IllegalArgumentException e) { + } + try { + iterator.following(TEXT.length()); + fail("should throw illegal argument exception"); + } catch (IllegalArgumentException e) { + } + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "isBoundary", + args = {int.class} + ) + public void testIsBoundary() { + try { + iterator.isBoundary(2); + fail("should throw illegal argument exception"); + } catch (IllegalArgumentException e) { + } + iterator.setText(TEXT); + assertTrue(iterator.isBoundary(2)); + assertFalse(iterator.isBoundary(1)); + assertTrue(iterator.isBoundary(0)); + try { + iterator.isBoundary(-1); + fail("should throw illegal argument exception"); + } catch (IllegalArgumentException e) { + } + try { + iterator.isBoundary(TEXT.length()); + fail("should throw illegal argument exception"); + } catch (IllegalArgumentException e) { + } + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "last", + args = {} + ) + public void testLast() { + assertEquals(0, iterator.last()); + iterator.setText(TEXT); + assertEquals(TEXT.length(), iterator.last()); + } + + /* + * Class under test for int next(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "next", + args = {int.class} + ) + public void testNextint() { + assertEquals(BreakIterator.DONE, iterator.next(3)); + iterator.setText(TEXT); + assertEquals(4, iterator.next(3)); + assertEquals(24, iterator.next(20)); + assertEquals(23, iterator.next(-1)); + assertEquals(-1, iterator.next(TEXT.length())); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "preceding", + args = {int.class} + ) + public void testPreceding() { + try { + iterator.preceding(2); + fail("should throw illegal argument exception"); + } catch (IllegalArgumentException e) { + } + iterator.setText(TEXT); + assertEquals(0, iterator.preceding(2)); + assertEquals(2, iterator.preceding(3)); + assertEquals(16, iterator.preceding(17)); + assertEquals(17, iterator.preceding(18)); + assertEquals(18, iterator.preceding(19)); + try { + iterator.preceding(-1); + fail("should throw illegal argument exception"); + } catch (IllegalArgumentException e) { + } + try { + iterator.preceding(TEXT.length()); + fail("should throw illegal argument exception"); + } catch (IllegalArgumentException e) { + } + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "previous", + args = {} + ) + public void testPrevious() { + assertEquals(-1, iterator.previous()); + iterator.setText(TEXT); + assertEquals(-1, iterator.previous()); + iterator.last(); + assertEquals(TEXT.length() - 1, iterator.previous()); + } + + /** + * @tests java.text.BreakIterator#getAvailableLocales(). Test of method + * java.text.BreakIterator#getAvailableLocales(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getAvailableLocales", + args = {} + ) + public void testGetAvailableLocales() { + try { + Locale[] locales = BreakIterator.getAvailableLocales(); + assertTrue("Array available locales is null", locales != null); + assertTrue("Array available locales is 0-length", + (locales != null && locales.length != 0)); + boolean found = false; + for (Locale l : locales) { + if (l.equals(Locale.US)) { + // expected + found = true; + } + } + assertTrue("At least locale " + Locale.US + " must be presented", + found); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /* + * Class under test for BreakIterator getCharacterInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCharacterInstance", + args = {} + ) + public void testGetCharacterInstance() { + BreakIterator.getCharacterInstance(); + } + + /* + * Class under test for BreakIterator getCharacterInstance(Locale) + */ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Doesn't verify exception.", + method = "getCharacterInstance", + args = {java.util.Locale.class} + ) + public void testGetCharacterInstanceLocale() { + BreakIterator it = BreakIterator.getCharacterInstance(Locale.US); + BreakIterator it2 = BreakIterator.getCharacterInstance(Locale.CHINA); + assertEquals(it, it2); + } + + /* + * Class under test for BreakIterator getLineInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getLineInstance", + args = {} + ) + public void testGetLineInstance() { + BreakIterator it = BreakIterator.getLineInstance(); + assertNotNull(it); + } + + /* + * @tests java.text.BreakIterator#getLineInstance(Locale) Class under test + * for BreakIterator getLineInstance(Locale) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify exception.", + method = "getLineInstance", + args = {java.util.Locale.class} + ) + public void testGetLineInstanceLocale() { + try { + BreakIterator it1 = BreakIterator + .getLineInstance(Locale.CANADA_FRENCH); + assertTrue("Incorrect BreakIterator", it1 != BreakIterator + .getLineInstance()); + BreakIterator it2 = BreakIterator.getLineInstance(new Locale( + "bad locale")); + assertTrue("Incorrect BreakIterator", it2 != BreakIterator + .getLineInstance()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /* + * Class under test for BreakIterator getSentenceInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getSentenceInstance", + args = {} + ) + public void testGetSentenceInstance() { + BreakIterator it = BreakIterator.getSentenceInstance(); + assertNotNull(it); + } + + /* + * Class under test for BreakIterator getSentenceInstance(Locale) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify exception.", + method = "getSentenceInstance", + args = {java.util.Locale.class} + ) + public void testGetSentenceInstanceLocale() { + BreakIterator it = BreakIterator.getSentenceInstance(Locale.US); + assertNotNull(it); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getText", + args = {} + ) + public void testGetText() { + assertEquals(new StringCharacterIterator(""), iterator.getText()); + iterator.setText(TEXT); + assertEquals(new StringCharacterIterator(TEXT), iterator.getText()); + } + + /* + * Class under test for BreakIterator getWordInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getWordInstance", + args = {} + ) + public void testGetWordInstance() { + BreakIterator it = BreakIterator.getWordInstance(); + assertNotNull(it); + } + + /* + * @tests java.text.BreakIterator#getWordInstance(Locale) Class under test + * for BreakIterator getWordInstance(Locale) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Doesn't verify exception.", + method = "getWordInstance", + args = {java.util.Locale.class} + ) + public void testGetWordInstanceLocale() { + try { + BreakIterator it1 = BreakIterator + .getWordInstance(Locale.CANADA_FRENCH); + assertTrue("Incorrect BreakIterator", it1 != BreakIterator + .getWordInstance()); + BreakIterator it2 = BreakIterator.getWordInstance(new Locale( + "bad locale")); + assertTrue("Incorrect BreakIterator", it2 != BreakIterator + .getWordInstance()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /* + * Class under test for void setText(CharacterIterator) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setText", + args = {java.text.CharacterIterator.class} + ) + public void testSetTextCharacterIterator() { + try { + iterator.setText((CharacterIterator) null); + fail(); + } catch (NullPointerException e) { + } + CharacterIterator it = new StringCharacterIterator("abc"); + iterator.setText(it); + assertSame(it, iterator.getText()); + } + + /* + * Class under test for void setText(String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setText", + args = {java.lang.String.class} + ) + public void testSetTextString() { + try { + iterator.setText((String) null); + fail(); + } catch (NullPointerException e) { + } + iterator.setText("abc"); + CharacterIterator it = new StringCharacterIterator("abc"); + assertEquals(it, iterator.getText()); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "next", + args = {} + ) + public void test_next() { + // Regression test for HARMONY-30 + BreakIterator bi = BreakIterator.getWordInstance(Locale.US); + bi.setText("This is the test, WordInstance"); + int n = bi.first(); + n = bi.next(); + assertEquals("Assert 0: next() returns incorrect value ", 4, n); + + assertEquals(BreakIterator.DONE, iterator.next()); + iterator.setText(TEXT); + assertEquals(2, iterator.next()); + } + + /** + * @tests java.text.BreakIterator.getShort(byte[], int) + * + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getShort", + args = {byte[].class, int.class} + ) + public void test_getShort() { + try { + MockBreakIterator.publicGetShort(null, 0); + fail("should throw NPE."); + } catch (NullPointerException e) { + } + try { + MockBreakIterator.publicGetShort(null, -1); + fail("should throw NPE."); + } catch (NullPointerException e) { + } + try { + MockBreakIterator.publicGetShort(new byte[] { 0, 0, 0, 1, 1 }, -1); + fail("should throw ArrayIndexOutOfBoundsException."); + } catch (ArrayIndexOutOfBoundsException e) { + } + try { + MockBreakIterator.publicGetShort(new byte[] { 0, 0 }, 1); + fail("should throw ArrayIndexOutOfBoundsException."); + } catch (ArrayIndexOutOfBoundsException e) { + } + assertEquals(0, MockBreakIterator + .publicGetShort(new byte[] { 0, 0 }, 0)); + assertEquals(1, MockBreakIterator + .publicGetShort(new byte[] { 0, 1 }, 0)); + assertEquals(-1, MockBreakIterator.publicGetShort(new byte[] { + (byte) 0xff, (byte) 0xff }, 0)); + assertEquals(1, MockBreakIterator.publicGetShort(new byte[] { 1, 0, 1, + 0 }, 1)); + assertEquals(1, MockBreakIterator.publicGetShort(new byte[] { 0, 0, 1, + 0 }, 1)); + assertEquals(1, MockBreakIterator.publicGetShort(new byte[] { 0, 0, 1, + 1 }, 1)); + assertEquals(257, MockBreakIterator.publicGetShort( + new byte[] { 0, 1, 1 }, 1)); + + // regression for Harmony-944 + try { + MockBreakIterator.publicGetShort(new byte[] { 0, 0 }, + Integer.MAX_VALUE); + fail("should throw ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException e) { + // expected + } + + try { + MockBreakIterator.publicGetShort(new byte[] { 0, 0 }, + Short.MAX_VALUE); + fail("should throw ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException e) { + // expected + } + } + + /** + * @tests java.text.BreakIterator.getInt(byte[], int) + * + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getInt", + args = {byte[].class, int.class} + ) + public void test_getInt() { + try { + MockBreakIterator.publicGetInt(null, 0); + fail("should throw NPE."); + } catch (NullPointerException e) { + } + try { + MockBreakIterator.publicGetInt(null, -1); + fail("should throw NPE."); + } catch (NullPointerException e) { + } + try { + MockBreakIterator.publicGetInt(new byte[] { 0, 0, 0, 1, 1 }, -1); + fail("should throw ArrayIndexOutOfBoundsException."); + } catch (ArrayIndexOutOfBoundsException e) { + } + try { + MockBreakIterator.publicGetInt(new byte[] { 0, 0, 0, 0 }, 1); + fail("should throw ArrayIndexOutOfBoundsException."); + } catch (ArrayIndexOutOfBoundsException e) { + } + assertEquals(0, MockBreakIterator.publicGetInt( + new byte[] { 0, 0, 0, 0 }, 0)); + assertEquals(1, MockBreakIterator.publicGetInt( + new byte[] { 0, 0, 0, 1 }, 0)); + assertEquals(-1, MockBreakIterator.publicGetInt(new byte[] { + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }, 0)); + assertEquals(1, MockBreakIterator.publicGetInt(new byte[] { 1, 0, 0, 0, + 1, 0 }, 1)); + assertEquals(1, MockBreakIterator.publicGetInt(new byte[] { 0, 0, 0, 0, + 1, 0 }, 1)); + assertEquals(1, MockBreakIterator.publicGetInt(new byte[] { 0, 0, 0, 0, + 1, 1 }, 1)); + assertEquals(257, MockBreakIterator.publicGetInt(new byte[] { 0, 0, 0, + 1, 1 }, 1)); + + // regression for Harmony-944 + try { + MockBreakIterator.publicGetInt(new byte[] { 0, 0 }, + Integer.MAX_VALUE); + fail("should throw ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException e) { + // expected + } + } + + /** + * @tests java.text.BreakIterator.getLong(byte[], int) + * + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getLong", + args = {byte[].class, int.class} + ) + public void test_getLong() { + try { + MockBreakIterator.publicGetLong(null, 0); + fail("should throw NPE."); + } catch (NullPointerException e) { + } + try { + MockBreakIterator.publicGetLong(null, -1); + fail("should throw NPE."); + } catch (NullPointerException e) { + } + try { + MockBreakIterator.publicGetLong( + new byte[] { 0, 0, 0, 0, 0, 0, 1, 1 }, -1); + fail("should throw ArrayIndexOutOfBoundsException."); + } catch (ArrayIndexOutOfBoundsException e) { + } + try { + MockBreakIterator.publicGetLong( + new byte[] { 0, 0, 0, 0, 0, 0, 1, 1 }, 1); + fail("should throw ArrayIndexOutOfBoundsException."); + } catch (ArrayIndexOutOfBoundsException e) { + } + assertEquals(0, MockBreakIterator.publicGetLong(new byte[] { 0, 0, 0, + 0, 0, 0, 0, 0 }, 0)); + assertEquals(1, MockBreakIterator.publicGetLong(new byte[] { 0, 0, 0, + 0, 0, 0, 0, 1 }, 0)); + assertEquals(-1, MockBreakIterator.publicGetLong(new byte[] { + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }, 0)); + assertEquals(1, MockBreakIterator.publicGetLong(new byte[] { 1, 0, 0, + 0, 0, 0, 0, 0, 1, 0 }, 1)); + assertEquals(1, MockBreakIterator.publicGetLong(new byte[] { 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0 }, 1)); + assertEquals(1, MockBreakIterator.publicGetLong(new byte[] { 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1 }, 1)); + assertEquals(257, MockBreakIterator.publicGetLong(new byte[] { 0, 0, 0, + 0, 0, 0, 0, 1, 1 }, 1)); + + // regression for Harmony-944 + try { + MockBreakIterator.publicGetLong(new byte[] { 0, 1 }, + Integer.MAX_VALUE); + fail("should throw ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException e) { + // expected + } + } + + /** + * @tests java.text.BreakIterator#getCharacterInstance(Locale) + */ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Verifies exception.", + method = "getCharacterInstance", + args = {java.util.Locale.class} + ) + public void testGetCharacterInstanceLocale_NPE() { + // Regression for HARMONY-265 + try { + BreakIterator.getCharacterInstance(null); + fail("BreakIterator.getCharacterInstance(null); should throw NullPointerException"); + } catch (NullPointerException e) { + } + } + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Verifies exception.", + method = "getLineInstance", + args = {java.util.Locale.class} + ) + public void testGetLineInstanceLocale_NPE() { + try { + BreakIterator.getLineInstance(null); + fail("BreakIterator.getLineInstance(null); should throw NullPointerException"); + } catch (NullPointerException e) { + } + } + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Verifies exception.", + method = "getSentenceInstance", + args = {java.util.Locale.class} + ) + public void testGetSentenceInstanceLocale_NPE() { + try { + BreakIterator.getSentenceInstance(null); + fail("BreakIterator.getSentenceInstance(null); should throw NullPointerException"); + } catch (NullPointerException e) { + } + } + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Verifies exception.", + method = "getWordInstance", + args = {java.util.Locale.class} + ) + public void testGetWordInstanceLocale_NPE() { + try { + BreakIterator.getWordInstance(null); + fail("BreakIterator.getWordInstance(null); should throw NullPointerException"); + } catch (NullPointerException e) { + } + } + + private static class MockBreakIterator extends BreakIterator { + public MockBreakIterator() { + super(); + } + + public static int publicGetInt(byte[] buf, int offset) { + return BreakIterator.getInt(buf, offset); + } + + public static long publicGetLong(byte[] buf, int offset) { + return BreakIterator.getLong(buf, offset); + } + + public static short publicGetShort(byte[] buf, int offset) { + return BreakIterator.getShort(buf, offset); + } + + public int current() { + return 0; + } + + public int first() { + return 0; + } + + public int following(int offset) { + return 0; + } + + public CharacterIterator getText() { + return null; + } + + public int last() { + return 0; + } + + public int next() { + return 0; + } + + public int next(int n) { + return 0; + } + + public int previous() { + return 0; + } + + public void setText(CharacterIterator newText) { + } + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/ChoiceFormatTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/ChoiceFormatTest.java new file mode 100644 index 0000000..3bec58a --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/ChoiceFormatTest.java @@ -0,0 +1,592 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.text.ChoiceFormat; +import java.text.FieldPosition; +import java.text.MessageFormat; +import java.text.ParsePosition; + + +@TestTargetClass(ChoiceFormat.class) +public class ChoiceFormatTest extends TestCase { + + double[] limits = new double[] { 0, 1, ChoiceFormat.nextDouble(1), + ChoiceFormat.nextDouble(2) }; + + String[] formats = new String[] { "Less than one", "one", + "Between one and two", "Greater than two" }; + + ChoiceFormat f1 = new ChoiceFormat(limits, formats); + + /** + * @tests java.text.ChoiceFormat#ChoiceFormat(double[], java.lang.String[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "ChoiceFormat", + args = {double[].class, java.lang.String[].class} + ) + public void test_Constructor$D$Ljava_lang_String() { + // Test for method java.text.ChoiceFormat(double [], java.lang.String + // []) + String formattedString; + double[] appleLimits = { 1, 2, 3, 4, 5 }; + String[] appleFormats = { "Tiny Apple", "Small Apple", "Medium Apple", + "Large Apple", "Huge Apple" }; + ChoiceFormat cf = new ChoiceFormat(appleLimits, appleFormats); + + formattedString = cf.format(Double.NEGATIVE_INFINITY); + assertTrue("a) Incorrect format returned: " + formattedString, + formattedString.equals("Tiny Apple")); + formattedString = cf.format(0.5d); + assertTrue("b) Incorrect format returned: " + formattedString, + formattedString.equals("Tiny Apple")); + formattedString = cf.format(1d); + assertTrue("c) Incorrect format returned: " + formattedString, + formattedString.equals("Tiny Apple")); + formattedString = cf.format(1.5d); + assertTrue("d) Incorrect format returned: " + formattedString, + formattedString.equals("Tiny Apple")); + formattedString = cf.format(2d); + assertTrue("e) Incorrect format returned: " + formattedString, + formattedString.equals("Small Apple")); + formattedString = cf.format(2.5d); + assertTrue("f) Incorrect format returned: " + formattedString, + formattedString.equals("Small Apple")); + formattedString = cf.format(3d); + assertTrue("g) Incorrect format returned: " + formattedString, + formattedString.equals("Medium Apple")); + formattedString = cf.format(4d); + assertTrue("h) Incorrect format returned: " + formattedString, + formattedString.equals("Large Apple")); + formattedString = cf.format(5d); + assertTrue("i) Incorrect format returned: " + formattedString, + formattedString.equals("Huge Apple")); + formattedString = cf.format(5.5d); + assertTrue("j) Incorrect format returned: " + formattedString, + formattedString.equals("Huge Apple")); + formattedString = cf.format(6.0d); + assertTrue("k) Incorrect format returned: " + formattedString, + formattedString.equals("Huge Apple")); + formattedString = cf.format(Double.POSITIVE_INFINITY); + assertTrue("l) Incorrect format returned: " + formattedString, + formattedString.equals("Huge Apple")); + } + + /** + * @tests java.text.ChoiceFormat#ChoiceFormat(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "ChoiceFormat", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() { + // Test for method java.text.ChoiceFormat(java.lang.String) + String formattedString; + String patternString = "-2#Inverted Orange| 0#No Orange| 0<Almost No Orange| 1#Normal Orange| 2#Expensive Orange"; + ChoiceFormat cf = new ChoiceFormat(patternString); + + formattedString = cf.format(Double.NEGATIVE_INFINITY); + assertTrue("a) Incorrect format returned: " + formattedString, + formattedString.equals("Inverted Orange")); + formattedString = cf.format(-3); + assertTrue("b) Incorrect format returned: " + formattedString, + formattedString.equals("Inverted Orange")); + formattedString = cf.format(-2); + assertTrue("c) Incorrect format returned: " + formattedString, + formattedString.equals("Inverted Orange")); + formattedString = cf.format(-1); + assertTrue("d) Incorrect format returned: " + formattedString, + formattedString.equals("Inverted Orange")); + formattedString = cf.format(-0); + assertTrue("e) Incorrect format returned: " + formattedString, + formattedString.equals("No Orange")); + formattedString = cf.format(0); + assertTrue("f) Incorrect format returned: " + formattedString, + formattedString.equals("No Orange")); + formattedString = cf.format(0.1); + assertTrue("g) Incorrect format returned: " + formattedString, + formattedString.equals("Almost No Orange")); + formattedString = cf.format(1); + assertTrue("h) Incorrect format returned: " + formattedString, + formattedString.equals("Normal Orange")); + formattedString = cf.format(1.5); + assertTrue("i) Incorrect format returned: " + formattedString, + formattedString.equals("Normal Orange")); + formattedString = cf.format(2); + assertTrue("j) Incorrect format returned: " + formattedString, + formattedString.equals("Expensive Orange")); + formattedString = cf.format(3); + assertTrue("k) Incorrect format returned: " + formattedString, + formattedString.equals("Expensive Orange")); + formattedString = cf.format(Double.POSITIVE_INFINITY); + assertTrue("l) Incorrect format returned: " + formattedString, + formattedString.equals("Expensive Orange")); + + } + + /** + * @tests java.text.ChoiceFormat#applyPattern(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "applyPattern", + args = {java.lang.String.class} + ) + public void test_applyPatternLjava_lang_String() { + // Test for method void + // java.text.ChoiceFormat.applyPattern(java.lang.String) + ChoiceFormat f = (ChoiceFormat) f1.clone(); + f.applyPattern("0#0|1#1"); + assertTrue("Incorrect limits", java.util.Arrays.equals(f.getLimits(), + new double[] { 0, 1 })); + assertTrue("Incorrect formats", java.util.Arrays.equals(f.getFormats(), + new String[] { "0", "1" })); + + // Regression for Harmony 540 + double[] choiceLimits = { -1, 0, 1, ChoiceFormat.nextDouble(1) }; + String[] choiceFormats = { "is negative", "is zero or fraction", + "is one", "is more than 1" }; + + f = new ChoiceFormat(""); + f + .applyPattern("-1#is negative|0#is zero or fraction|1#is one|1<is more than 1"); + assertTrue("Incorrect limits", java.util.Arrays.equals(f.getLimits(), + choiceLimits)); + assertTrue("Incorrect formats", java.util.Arrays.equals(f.getFormats(), + choiceFormats)); + + f = new ChoiceFormat(""); + try { + f + .applyPattern("-1#is negative|0#is zero or fraction|-1#is one|1<is more than 1"); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expected + } + + f = new ChoiceFormat(""); + try { + f + .applyPattern("-1is negative|0#is zero or fraction|1#is one|1<is more than 1"); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expected + } + + f = new ChoiceFormat(""); + f + .applyPattern("-1<is negative|0#is zero or fraction|1#is one|1<is more than 1"); + choiceLimits[0] = ChoiceFormat.nextDouble(-1); + assertTrue("Incorrect limits", java.util.Arrays.equals(f.getLimits(), + choiceLimits)); + assertTrue("Incorrect formats", java.util.Arrays.equals(f.getFormats(), + choiceFormats)); + + f = new ChoiceFormat(""); + f + .applyPattern("-1#is negative|0#is zero or fraction|1#is one|1<is more than 1"); + String str = "org.apache.harmony.tests.java.text.ChoiceFormat"; + f.applyPattern(str); + String ptrn = f.toPattern(); + assertEquals("Return value should be empty string for invalid pattern", + 0, ptrn.length()); + } + + /** + * @tests java.text.ChoiceFormat#clone() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + // Test for method java.lang.Object java.text.ChoiceFormat.clone() + ChoiceFormat f = (ChoiceFormat) f1.clone(); + assertTrue("Not equal", f.equals(f1)); + f.setChoices(new double[] { 0, 1, 2 }, new String[] { "0", "1", "2" }); + assertTrue("Equal", !f.equals(f1)); + } + + /** + * @tests java.text.ChoiceFormat#equals(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + // Test for method boolean + // java.text.ChoiceFormat.equals(java.lang.Object) + + String patternString = "-2#Inverted Orange| 0#No Orange| 0<Almost No Orange| 1#Normal Orange| 2#Expensive Orange"; + double[] appleLimits = { 1, 2, 3, 4, 5 }; + String[] appleFormats = { "Tiny Apple", "Small Apple", "Medium Apple", + "Large Apple", "Huge Apple" }; + double[] orangeLimits = { -2, 0, ChoiceFormat.nextDouble(0), 1, 2 }; + String[] orangeFormats = { "Inverted Orange", "No Orange", + "Almost No Orange", "Normal Orange", "Expensive Orange" }; + + ChoiceFormat appleChoiceFormat = new ChoiceFormat(appleLimits, + appleFormats); + ChoiceFormat orangeChoiceFormat = new ChoiceFormat(orangeLimits, + orangeFormats); + ChoiceFormat orangeChoiceFormat2 = new ChoiceFormat(patternString); + ChoiceFormat hybridChoiceFormat = new ChoiceFormat(appleLimits, + orangeFormats); + + assertTrue("Apples should not equal oranges", !appleChoiceFormat + .equals(orangeChoiceFormat)); + assertTrue("Different limit list--should not appear as equal", + !orangeChoiceFormat.equals(hybridChoiceFormat)); + assertTrue("Different format list--should not appear as equal", + !appleChoiceFormat.equals(hybridChoiceFormat)); + assertTrue("Should be equal--identical format", appleChoiceFormat + .equals(appleChoiceFormat)); + assertTrue("Should be equals--same limits, same formats", + orangeChoiceFormat.equals(orangeChoiceFormat2)); + + ChoiceFormat f2 = new ChoiceFormat( + "0#Less than one|1#one|1<Between one and two|2<Greater than two"); + assertTrue("Not equal", f1.equals(f2)); + } + + /** + * @tests java.text.ChoiceFormat#format(double, java.lang.StringBuffer, + * java.text.FieldPosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {double.class, java.lang.StringBuffer.class, java.text.FieldPosition.class} + ) + public void test_formatDLjava_lang_StringBufferLjava_text_FieldPosition() { + // Test for method java.lang.StringBuffer + // java.text.ChoiceFormat.format(double, java.lang.StringBuffer, + // java.text.FieldPosition) + FieldPosition field = new FieldPosition(0); + StringBuffer buf = new StringBuffer(); + String r = f1.format(-1, buf, field).toString(); + assertEquals("Wrong choice for -1", "Less than one", r); + buf.setLength(0); + r = f1.format(0, buf, field).toString(); + assertEquals("Wrong choice for 0", "Less than one", r); + buf.setLength(0); + r = f1.format(1, buf, field).toString(); + assertEquals("Wrong choice for 1", "one", r); + buf.setLength(0); + r = f1.format(2, buf, field).toString(); + assertEquals("Wrong choice for 2", "Between one and two", r); + buf.setLength(0); + r = f1.format(3, buf, field).toString(); + assertEquals("Wrong choice for 3", "Greater than two", r); + + // Regression test for HARMONY-1081 + assertEquals(0, new ChoiceFormat("|").format(Double.NaN, + new StringBuffer(), new FieldPosition(6)).length()); + assertEquals(0, new ChoiceFormat("|").format(1, new StringBuffer(), + new FieldPosition(6)).length()); + assertEquals("Less than one", f1.format(Double.NaN, new StringBuffer(), + field).toString()); + } + + /** + * @tests java.text.ChoiceFormat#format(long, java.lang.StringBuffer, + * java.text.FieldPosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {long.class, java.lang.StringBuffer.class, java.text.FieldPosition.class} + ) + public void test_formatJLjava_lang_StringBufferLjava_text_FieldPosition() { + // Test for method java.lang.StringBuffer + // java.text.ChoiceFormat.format(long, java.lang.StringBuffer, + // java.text.FieldPosition) + FieldPosition field = new FieldPosition(0); + StringBuffer buf = new StringBuffer(); + String r = f1.format(0.5, buf, field).toString(); + assertEquals("Wrong choice for 0.5", "Less than one", r); + buf.setLength(0); + r = f1.format(1.5, buf, field).toString(); + assertEquals("Wrong choice for 1.5", "Between one and two", r); + buf.setLength(0); + r = f1.format(2.5, buf, field).toString(); + assertEquals("Wrong choice for 2.5", "Greater than two", r); + } + + /** + * @tests java.text.ChoiceFormat#getFormats() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getFormats", + args = {} + ) + public void test_getFormats() { + // Test for method java.lang.Object [] + // java.text.ChoiceFormat.getFormats() + String[] orgFormats = (String[]) formats.clone(); + String[] f = (String[]) f1.getFormats(); + assertTrue("Wrong formats", f.equals(formats)); + f[0] = "Modified"; + assertTrue("Formats copied", !f.equals(orgFormats)); + } + + /** + * @tests java.text.ChoiceFormat#getLimits() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getLimits", + args = {} + ) + public void test_getLimits() { + // Test for method double [] java.text.ChoiceFormat.getLimits() + double[] orgLimits = (double[]) limits.clone(); + double[] l = f1.getLimits(); + assertTrue("Wrong limits", l.equals(limits)); + l[0] = 3.14527; + assertTrue("Limits copied", !l.equals(orgLimits)); + } + + /** + * @tests java.text.ChoiceFormat#hashCode() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + // Test for method int java.text.ChoiceFormat.hashCode() + ChoiceFormat f2 = new ChoiceFormat( + "0#Less than one|1#one|1<Between one and two|2<Greater than two"); + assertTrue("Different hash", f1.hashCode() == f2.hashCode()); + } + + /** + * @tests java.text.ChoiceFormat#nextDouble(double) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "nextDouble", + args = {double.class} + ) + public void test_nextDoubleD() { + // Test for method double java.text.ChoiceFormat.nextDouble(double) + assertTrue("Not greater 5", ChoiceFormat.nextDouble(5) > 5); + assertTrue("Not greater 0", ChoiceFormat.nextDouble(0) > 0); + assertTrue("Not greater -5", ChoiceFormat.nextDouble(-5) > -5); + assertTrue("Not NaN", Double.isNaN(ChoiceFormat.nextDouble(Double.NaN))); + } + + /** + * @tests java.text.ChoiceFormat#nextDouble(double, boolean) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "nextDouble", + args = {double.class, boolean.class} + ) + public void test_nextDoubleDZ() { + // Test for method double java.text.ChoiceFormat.nextDouble(double, + // boolean) + assertTrue("Not greater 0", ChoiceFormat.nextDouble(0, true) > 0); + assertTrue("Not less 0", ChoiceFormat.nextDouble(0, false) < 0); + } + + /** + * @tests java.text.ChoiceFormat#parse(java.lang.String, + * java.text.ParsePosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "parse", + args = {java.lang.String.class, java.text.ParsePosition.class} + ) + public void test_parseLjava_lang_StringLjava_text_ParsePosition() { + // Test for method java.lang.Number + // java.text.ChoiceFormat.parse(java.lang.String, + // java.text.ParsePosition) + ChoiceFormat format = new ChoiceFormat("1#one|2#two|3#three"); + assertEquals("Case insensitive", 0, format.parse("One", + new ParsePosition(0)).intValue()); + + ParsePosition pos = new ParsePosition(0); + Number result = f1.parse("Greater than two", pos); + assertTrue("Not a Double1", result instanceof Double); + assertTrue("Wrong value ~>2", result.doubleValue() == ChoiceFormat + .nextDouble(2)); + assertEquals("Wrong position ~16", 16, pos.getIndex()); + pos = new ParsePosition(0); + assertTrue("Incorrect result", Double.isNaN(f1.parse("12one", pos) + .doubleValue())); + assertEquals("Wrong position ~0", 0, pos.getIndex()); + pos = new ParsePosition(2); + result = f1.parse("12one and two", pos); + assertTrue("Not a Double2", result instanceof Double); + assertEquals("Ignored parse position", 1.0D, result.doubleValue(), 0.0D); + assertEquals("Wrong position ~5", 5, pos.getIndex()); + } + + /** + * @tests java.text.ChoiceFormat#previousDouble(double) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "previousDouble", + args = {double.class} + ) + public void test_previousDoubleD() { + // Test for method double java.text.ChoiceFormat.previousDouble(double) + assertTrue("Not less 5", ChoiceFormat.previousDouble(5) < 5); + assertTrue("Not less 0", ChoiceFormat.previousDouble(0) < 0); + assertTrue("Not less -5", ChoiceFormat.previousDouble(-5) < -5); + assertTrue("Not NaN", Double.isNaN(ChoiceFormat + .previousDouble(Double.NaN))); + } + + /** + * @tests java.text.ChoiceFormat#setChoices(double[], java.lang.String[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setChoices", + args = {double[].class, java.lang.String[].class} + ) + public void test_setChoices$D$Ljava_lang_String() { + // Test for method void java.text.ChoiceFormat.setChoices(double [], + // java.lang.String []) + ChoiceFormat f = (ChoiceFormat) f1.clone(); + double[] l = new double[] { 0, 1 }; + String[] fs = new String[] { "0", "1" }; + f.setChoices(l, fs); + assertTrue("Limits copied", f.getLimits() == l); + assertTrue("Formats copied", f.getFormats() == fs); + } + + /** + * @tests java.text.ChoiceFormat#toPattern() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "toPattern", + args = {} + ) + public void test_toPattern() { + // Regression for HARMONY-59 + ChoiceFormat cf = new ChoiceFormat(""); + assertEquals("", cf.toPattern()); + + cf = new ChoiceFormat("-1#NEGATIVE_ONE|0#ZERO|1#ONE|1<GREATER_THAN_ONE"); + assertEquals("-1.0#NEGATIVE_ONE|0.0#ZERO|1.0#ONE|1.0<GREATER_THAN_ONE", + cf.toPattern()); + + MessageFormat mf = new MessageFormat("CHOICE {1,choice}"); + String ptrn = mf.toPattern(); + assertEquals("Unused message format returning incorrect pattern", + "CHOICE {1,choice,}", ptrn); + + String pattern = f1.toPattern(); + assertTrue( + "Wrong pattern: " + pattern, + pattern + .equals("0.0#Less than one|1.0#one|1.0<Between one and two|2.0<Greater than two")); + + cf = new ChoiceFormat( + "-1#is negative| 0#is zero or fraction | 1#is one |1.0<is 1+|2#is two |2<is more than 2."); + String str = "org.apache.harmony.tests.java.lang.share.MyResources2"; + cf.applyPattern(str); + ptrn = cf.toPattern(); + assertEquals("Return value should be empty string for invalid pattern", + 0, ptrn.length()); + } + + /** + * @tests java.text.ChoiceFormat#format(long) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {long.class} + ) + public void test_formatL() { + ChoiceFormat fmt = new ChoiceFormat( + "-1#NEGATIVE_ONE|0#ZERO|1#ONE|1<GREATER_THAN_ONE"); + + assertEquals("NEGATIVE_ONE", fmt.format(Long.MIN_VALUE)); + assertEquals("NEGATIVE_ONE", fmt.format(-1)); + assertEquals("ZERO", fmt.format(0)); + assertEquals("ONE", fmt.format(1)); + assertEquals("GREATER_THAN_ONE", fmt.format(Long.MAX_VALUE)); + } + + /** + * @tests java.text.ChoiceFormat#format(double) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {double.class} + ) + public void test_formatD() { + ChoiceFormat fmt = new ChoiceFormat( + "-1#NEGATIVE_ONE|0#ZERO|1#ONE|1<GREATER_THAN_ONE"); + assertEquals("NEGATIVE_ONE", fmt.format(Double.NEGATIVE_INFINITY)); + assertEquals("NEGATIVE_ONE", fmt.format(-999999999D)); + assertEquals("NEGATIVE_ONE", fmt.format(-1.1)); + assertEquals("NEGATIVE_ONE", fmt.format(-1.0)); + assertEquals("NEGATIVE_ONE", fmt.format(-0.9)); + assertEquals("ZERO", fmt.format(0.0)); + assertEquals("ZERO", fmt.format(0.9)); + assertEquals("ONE", fmt.format(1.0)); + assertEquals("GREATER_THAN_ONE", fmt.format(1.1)); + assertEquals("GREATER_THAN_ONE", fmt.format(999999999D)); + assertEquals("GREATER_THAN_ONE", fmt.format(Double.POSITIVE_INFINITY)); + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/CollationElementIteratorTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/CollationElementIteratorTest.java new file mode 100644 index 0000000..19dbb11 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/CollationElementIteratorTest.java @@ -0,0 +1,296 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.text.CollationElementIterator; +import java.text.Collator; +import java.text.RuleBasedCollator; +import java.text.StringCharacterIterator; +import java.util.Locale; + +/** + * Test CollationElementIterator + * + * Only test normal condition. + * + */ +@TestTargetClass(CollationElementIterator.class) +public class CollationElementIteratorTest extends TestCase { + + private RuleBasedCollator coll; + + protected void setUp() { + coll = (RuleBasedCollator) Collator.getInstance(Locale.US); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getOffset", + args = {} + ) + public void testGetOffset() { + String text = "abc"; + CollationElementIterator iterator = coll + .getCollationElementIterator(text); + int[] offsets = { 0, 1, 2, 3 }; + int offset = iterator.getOffset(); + int i = 0; + assertEquals(offsets[i++], offset); + while (offset != text.length()) { + iterator.next(); + offset = iterator.getOffset(); + assertEquals(offsets[i++], offset); + } + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "next", + args = {} + ) + public void testNext() { + String text = "abc"; + CollationElementIterator iterator = coll + .getCollationElementIterator(text); + int[] orders = new int[text.length()]; + int order = iterator.next(); + int i = 0; + while (order != CollationElementIterator.NULLORDER) { + orders[i++] = order; + order = iterator.next(); + } + + int offset = iterator.getOffset(); + assertEquals(text.length(), offset); + order = iterator.previous(); + + while (order != CollationElementIterator.NULLORDER) { + assertEquals(orders[--i], order); + order = iterator.previous(); + } + + assertEquals(0, iterator.getOffset()); + } + + /** + * @tests java.text.CollationElementIterator#previous() Test of method + * java.text.CollationElementIterator#previous(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "previous", + args = {} + ) + public void testPrevious() { + String text = "abc"; + CollationElementIterator iterator = coll + .getCollationElementIterator(text); + int[] orders = new int[text.length()]; + int order = iterator.next(); + int i = 0; + while (order != CollationElementIterator.NULLORDER) { + orders[i++] = order; + order = iterator.next(); + } + + int offset = iterator.getOffset(); + assertEquals(text.length(), offset); + order = iterator.previous(); + + while (order != CollationElementIterator.NULLORDER) { + assertEquals(orders[--i], order); + order = iterator.previous(); + } + + assertEquals(0, iterator.getOffset()); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "reset", + args = {} + ) + public void testReset() { + String text = "abc"; + CollationElementIterator iterator = coll + .getCollationElementIterator(text); + int[] orders = new int[text.length()]; + int order = iterator.next(); + int i = 0; + while (order != CollationElementIterator.NULLORDER) { + orders[i++] = order; + order = iterator.next(); + } + + int offset = iterator.getOffset(); + assertEquals(text.length(), offset); + + iterator.reset(); + assertEquals(0, iterator.getOffset()); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMaxExpansion", + args = {int.class} + ) + public void testGetMaxExpansion() { + String text = "cha"; + RuleBasedCollator rbColl = (RuleBasedCollator) Collator + .getInstance(new Locale("es", "", "TRADITIONAL")); + CollationElementIterator iterator = rbColl + .getCollationElementIterator(text); + int order = iterator.next(); + while (order != CollationElementIterator.NULLORDER) { + assertEquals(1, iterator.getMaxExpansion(order)); + order = iterator.next(); + } + + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "primaryOrder", + args = {int.class} + ) + public void testPrimaryOrder() { + RuleBasedCollator rbColl = (RuleBasedCollator) Collator + .getInstance(new Locale("de", "DE")); + String text = "\u00e6"; + CollationElementIterator iterator = rbColl + .getCollationElementIterator(text); + int order = iterator.next(); + int pOrder = CollationElementIterator.primaryOrder(order); + CollationElementIterator iterator2 = rbColl + .getCollationElementIterator("ae"); + int order2 = iterator2.next(); + int pOrder2 = CollationElementIterator.primaryOrder(order2); + assertEquals(pOrder, pOrder2); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "secondaryOrder", + args = {int.class} + ) + public void testSecondaryOrder() { + RuleBasedCollator rbColl = (RuleBasedCollator) Collator + .getInstance(new Locale("fr", "FR")); + String text = "a\u00e0"; + CollationElementIterator iterator = rbColl + .getCollationElementIterator(text); + int order = iterator.next(); + int sOrder1 = CollationElementIterator.secondaryOrder(order); + + order = iterator.next(); + int sOrder2 = CollationElementIterator.secondaryOrder(order); + + assertEquals(sOrder1, sOrder2); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "tertiaryOrder", + args = {int.class} + ) + public void testTertiaryOrder() { + RuleBasedCollator rbColl = (RuleBasedCollator) Collator + .getInstance(new Locale("fr", "FR")); + String text = "abAB"; + CollationElementIterator iterator = rbColl + .getCollationElementIterator(text); + int order = iterator.next(); + int tOrder1 = CollationElementIterator.tertiaryOrder(order); + order = iterator.next(); + int tOrder2 = CollationElementIterator.tertiaryOrder(order); + assertEquals(tOrder1, tOrder2); + + order = iterator.next(); + tOrder1 = CollationElementIterator.tertiaryOrder(order); + order = iterator.next(); + tOrder2 = CollationElementIterator.tertiaryOrder(order); + assertEquals(tOrder1, tOrder2); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setOffset", + args = {int.class} + ) + public void testSetOffset() { + RuleBasedCollator rbColl = (RuleBasedCollator) Collator + .getInstance(new Locale("es", "", "TRADITIONAL")); + String text = "cha"; + CollationElementIterator iterator = rbColl + .getCollationElementIterator(text); + iterator.setOffset(1); + assertEquals(1, iterator.getOffset()); + } + + /* + * Class under test for void setText(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setText", + args = {java.lang.String.class} + ) + public void testSetTextString() { + RuleBasedCollator rbColl = (RuleBasedCollator) Collator + .getInstance(new Locale("es", "", "TRADITIONAL")); + String text = "caa"; + CollationElementIterator iterator = rbColl + .getCollationElementIterator(text); + iterator.setOffset(1); + assertEquals(1, iterator.getOffset()); + iterator.setText("cha"); + iterator.setOffset(1); + assertEquals(1, iterator.getOffset()); + } + + /* + * Class under test for void setText(java.text.CharacterIterator) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setText", + args = {java.text.CharacterIterator.class} + ) + public void testSetTextCharacterIterator() { + RuleBasedCollator rbColl = (RuleBasedCollator) Collator + .getInstance(new Locale("es", "", "TRADITIONAL")); + String text = "caa"; + CollationElementIterator iterator = rbColl + .getCollationElementIterator(text); + iterator.setOffset(1); + assertEquals(1, iterator.getOffset()); + iterator.setText(new StringCharacterIterator("cha")); + iterator.setOffset(1); + assertEquals(1, iterator.getOffset()); + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/CollationKeyTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/CollationKeyTest.java new file mode 100644 index 0000000..d1a7c42 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/CollationKeyTest.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; + +import java.text.CollationKey; +import java.text.Collator; +import java.text.ParseException; +import java.text.RuleBasedCollator; +import java.util.Arrays; + + +@TestTargetClass(CollationKey.class) +public class CollationKeyTest extends junit.framework.TestCase { + + /** + * @tests java.text.CollationKey#compareTo(java.text.CollationKey) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "compareTo", + args = {java.text.CollationKey.class} + ) + public void test_compareToLjava_text_CollationKey() { + Collator collator = Collator.getInstance(); + collator.setStrength(Collator.PRIMARY); + CollationKey key1 = collator.getCollationKey("abc"); + CollationKey key2 = collator.getCollationKey("ABC"); + assertEquals("Should be equal", 0, key1.compareTo(key2)); + } + + /** + * @tests java.text.CollationKey#compareTo(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "compareTo", + args = {java.lang.Object.class} + ) + public void test_compareToLjava_lang_Object() { + // Test for method int + // java.text.CollationKey.compareTo(java.lang.Object) + Collator collator = Collator.getInstance(); + collator.setStrength(Collator.PRIMARY); + CollationKey key1 = collator.getCollationKey("abc"); + CollationKey key2 = collator.getCollationKey("ABC"); + assertEquals("Should be equal", 0, key1.compareTo(key2)); + } + + /** + * @tests java.text.CollationKey#equals(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + Collator collator = Collator.getInstance(); + collator.setStrength(Collator.PRIMARY); + CollationKey key1 = collator.getCollationKey("abc"); + CollationKey key2 = collator.getCollationKey("ABC"); + assertTrue("Should be equal", key1.equals(key2)); + } + + /** + * @tests java.text.CollationKey#getSourceString() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getSourceString", + args = {} + ) + public void test_getSourceString() { + Collator collator = Collator.getInstance(); + collator.setStrength(Collator.PRIMARY); + assertTrue("Wrong source string1", collator.getCollationKey("abc") + .getSourceString() == "abc"); + assertTrue("Wrong source string2", collator.getCollationKey("ABC") + .getSourceString() == "ABC"); + } + + /** + * @tests java.text.CollationKey#hashCode() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + Collator collator = Collator.getInstance(); + collator.setStrength(Collator.PRIMARY); + CollationKey key1 = collator.getCollationKey("abc"); + CollationKey key2 = collator.getCollationKey("ABC"); + assertTrue("Should be equal", key1.hashCode() == key2.hashCode()); + } + + /** + * @tests java.text.CollationKey#toByteArray() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "toByteArray", + args = {} + ) + @KnownFailure("This test fails on Harmony ClassLibrary.") + public void test_toByteArray() { + // Test for method byte [] java.text.CollationKey.toByteArray() + Collator collator = Collator.getInstance(); + collator.setStrength(Collator.PRIMARY); + CollationKey key1 = collator.getCollationKey("abc"); + byte[] bytes = key1.toByteArray(); + assertTrue("Not enough bytes", bytes.length >= 3); + + try { + collator = new RuleBasedCollator("= 1 , 2 ; 3 , 4 < 5 ; 6 , 7"); + } catch (ParseException e) { + fail("ParseException"); + return; + } + bytes = collator.getCollationKey("1234567").toByteArray(); + /* + * CollationElementIterator it = + * ((RuleBasedCollator)collator).getCollationElementIterator("1234567"); + * int order; while ((order = it.next()) != + * CollationElementIterator.NULLORDER) { + * System.out.println(Integer.toHexString(order)); } for (int i=0; i<bytes.length; + * i+=2) { System.out.print(Integer.toHexString(bytes[i]) + + * Integer.toHexString(bytes[i+1]) + " "); } System.out.println(); + */ + byte[] result = new byte[] { 0, 2, 0, 2, 0, 2, 0, 0, 0, 3, 0, 3, 0, 1, + 0, 2, 0, 2, 0, 0, 0, 4, 0, 4, 0, 1, 0, 1, 0, 2 }; + assertTrue("Wrong bytes", Arrays.equals(bytes, result)); + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/CollatorTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/CollatorTest.java new file mode 100644 index 0000000..04a2b47 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/CollatorTest.java @@ -0,0 +1,516 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargets; + +import java.io.UnsupportedEncodingException; +import java.text.CollationKey; +import java.text.Collator; +import java.text.ParseException; +import java.text.RuleBasedCollator; +import java.util.Locale; + +@TestTargetClass(Collator.class) +public class CollatorTest extends junit.framework.TestCase { + + /** + * @tests java.text.Collator#clone() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + Collator c = Collator.getInstance(Locale.GERMAN); + Collator c2 = (Collator) c.clone(); + assertTrue("Clones answered false to equals", c.equals(c2)); + assertTrue("Clones were equivalent", c != c2); + } + + /** + * @tests java.text.Collator#compare(java.lang.Object, java.lang.Object) + */ + @TestTargets({ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "compare", + args = {java.lang.Object.class, java.lang.Object.class} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setStrength", + args = {int.class} + ) + }) + public void test_compareLjava_lang_ObjectLjava_lang_Object() { + Collator c = Collator.getInstance(Locale.FRENCH); + Object o, o2; + + c.setStrength(Collator.IDENTICAL); + o = "E"; + o2 = "F"; + assertTrue("a) Failed on primary difference", c.compare(o, o2) < 0); + o = "e"; + o2 = "\u00e9"; + assertTrue("a) Failed on secondary difference", c.compare(o, o2) < 0); + o = "e"; + o2 = "E"; + assertTrue("a) Failed on tertiary difference", c.compare(o, o2) < 0); + o = "\u0001"; + o2 = "\u0002"; + assertTrue("a) Failed on identical", c.compare(o, o2) < 0); + o = "e"; + o2 = "e"; + assertEquals("a) Failed on equivalence", 0, c.compare(o, o2)); + assertTrue("a) Failed on primary expansion", + c.compare("\u01db", "v") < 0); + + c.setStrength(Collator.TERTIARY); + o = "E"; + o2 = "F"; + assertTrue("b) Failed on primary difference", c.compare(o, o2) < 0); + o = "e"; + o2 = "\u00e9"; + assertTrue("b) Failed on secondary difference", c.compare(o, o2) < 0); + o = "e"; + o2 = "E"; + assertTrue("b) Failed on tertiary difference", c.compare(o, o2) < 0); + o = "\u0001"; + o2 = "\u0002"; + assertEquals("b) Failed on identical", 0, c.compare(o, o2)); + o = "e"; + o2 = "e"; + assertEquals("b) Failed on equivalence", 0, c.compare(o, o2)); + + c.setStrength(Collator.SECONDARY); + o = "E"; + o2 = "F"; + assertTrue("c) Failed on primary difference", c.compare(o, o2) < 0); + o = "e"; + o2 = "\u00e9"; + assertTrue("c) Failed on secondary difference", c.compare(o, o2) < 0); + o = "e"; + o2 = "E"; + assertEquals("c) Failed on tertiary difference", 0, c.compare(o, o2)); + o = "\u0001"; + o2 = "\u0002"; + assertEquals("c) Failed on identical", 0, c.compare(o, o2)); + o = "e"; + o2 = "e"; + assertEquals("c) Failed on equivalence", 0, c.compare(o, o2)); + + c.setStrength(Collator.PRIMARY); + o = "E"; + o2 = "F"; + assertTrue("d) Failed on primary difference", c.compare(o, o2) < 0); + o = "e"; + o2 = "\u00e9"; + assertEquals("d) Failed on secondary difference", 0, c.compare(o, o2)); + o = "e"; + o2 = "E"; + assertEquals("d) Failed on tertiary difference", 0, c.compare(o, o2)); + o = "\u0001"; + o2 = "\u0002"; + assertEquals("d) Failed on identical", 0, c.compare(o, o2)); + o = "e"; + o2 = "e"; + assertEquals("d) Failed on equivalence", 0, c.compare(o, o2)); + + try { + c.compare("e", new StringBuffer("Blah")); + } catch (ClassCastException e) { + // correct + return; + } + fail("Failed to throw ClassCastException"); + } + + /** + * @tests java.text.Collator#equals(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + Collator c = Collator.getInstance(Locale.ENGLISH); + Collator c2 = (Collator) c.clone(); + assertTrue("Cloned collators not equal", c.equals(c2)); + c2.setStrength(Collator.SECONDARY); + assertTrue("Collators with different strengths equal", !c.equals(c2)); + } + + /** + * @tests java.text.Collator#equals(java.lang.String, java.lang.String) + */ + @TestTargets({ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.String.class, java.lang.String.class} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setStrength", + args = {int.class} + ) + }) + public void test_equalsLjava_lang_StringLjava_lang_String() { + Collator c = Collator.getInstance(Locale.FRENCH); + + c.setStrength(Collator.IDENTICAL); + assertTrue("a) Failed on primary difference", !c.equals("E", "F")); + assertTrue("a) Failed on secondary difference", !c + .equals("e", "\u00e9")); + assertTrue("a) Failed on tertiary difference", !c.equals("e", "E")); + assertTrue("a) Failed on identical", !c.equals("\u0001", "\u0002")); + assertTrue("a) Failed on equivalence", c.equals("e", "e")); + + c.setStrength(Collator.TERTIARY); + assertTrue("b) Failed on primary difference", !c.equals("E", "F")); + assertTrue("b) Failed on secondary difference", !c + .equals("e", "\u00e9")); + assertTrue("b) Failed on tertiary difference", !c.equals("e", "E")); + assertTrue("b) Failed on identical", c.equals("\u0001", "\u0002")); + assertTrue("b) Failed on equivalence", c.equals("e", "e")); + + c.setStrength(Collator.SECONDARY); + assertTrue("c) Failed on primary difference", !c.equals("E", "F")); + assertTrue("c) Failed on secondary difference", !c + .equals("e", "\u00e9")); + assertTrue("c) Failed on tertiary difference", c.equals("e", "E")); + assertTrue("c) Failed on identical", c.equals("\u0001", "\u0002")); + assertTrue("c) Failed on equivalence", c.equals("e", "e")); + + c.setStrength(Collator.PRIMARY); + assertTrue("d) Failed on primary difference", !c.equals("E", "F")); + assertTrue("d) Failed on secondary difference", c.equals("e", "\u00e9")); + assertTrue("d) Failed on tertiary difference", c.equals("e", "E")); + assertTrue("d) Failed on identical", c.equals("\u0001", "\u0002")); + assertTrue("d) Failed on equivalence", c.equals("e", "e")); + } + + /** + * @tests java.text.Collator#getAvailableLocales() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "getAvailableLocales", + args = {} + ) + public void test_getAvailableLocales() { + Locale[] locales = Collator.getAvailableLocales(); + assertTrue("No locales", locales.length > 0); + boolean hasUS = false; + for (int i = locales.length; --i >= 0;) { + Collator c1 = Collator.getInstance(locales[i]); + assertTrue("Doesn't work", c1.compare("a", "b") < 0); + assertTrue("Wrong decomposition", + c1.getDecomposition() == Collator.NO_DECOMPOSITION); + assertTrue("Wrong strength", c1.getStrength() == Collator.TERTIARY); + // The default decomposition for collators created with getInstance + // is NO_DECOMPOSITION where collators created from rules have + // CANONICAL_DECOMPOSITION. Verified on RI. + c1.setDecomposition(Collator.CANONICAL_DECOMPOSITION); + if (locales[i].equals(Locale.US)) { + hasUS = true; + } + if (c1 instanceof RuleBasedCollator) { + String rule = ""; + Collator temp = null; + try { + rule = ((RuleBasedCollator) c1).getRules(); + temp = new RuleBasedCollator(rule); + } catch (ParseException e) { + fail(e.getMessage() + " for rule: \"" + rule + "\""); + } + assertTrue("Can't recreate: " + locales[i], temp.equals(c1)); + } + } + assertTrue("en_US locale not available", hasUS); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Collator", + args = {} + ) + public void test_Constructor() { + TestCollator collator = new TestCollator(); + assertEquals(Collator.TERTIARY, collator.getStrength()); + } + + /** + * @tests java.text.Collator#getDecomposition() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "getDecomposition", + args = {} + ) + public void test_getDecomposition() { + RuleBasedCollator collator; + try { + collator = new RuleBasedCollator("; \u0300 < a, A < b < c < d"); + } catch (ParseException e) { + fail("ParseException"); + return; + } + assertTrue("Wrong default", + collator.getDecomposition() == Collator.CANONICAL_DECOMPOSITION); + + collator.setDecomposition(Collator.NO_DECOMPOSITION); + assertEquals(Collator.NO_DECOMPOSITION, collator.getDecomposition()); + + // BEGIN android-removed + // Android doesn't support full decomposition + // collator.setDecomposition(Collator.FULL_DECOMPOSITION); + // assertEquals(Collator.FULL_DECOMPOSITION, collator.getDecomposition()); + // EN android-removed + } + + /** + * @tests java.text.Collator#getInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getInstance", + args = {} + ) + public void test_getInstance() { + Collator c1 = Collator.getInstance(); + Collator c2 = Collator.getInstance(Locale.getDefault()); + assertTrue("Wrong locale", c1.equals(c2)); + } + + /** + * @tests java.text.Collator#getInstance(java.util.Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getInstance", + args = {java.util.Locale.class} + ) + public void test_getInstanceLjava_util_Locale() { + assertTrue("Used to test", true); + } + + /** + * @tests java.text.Collator#getStrength() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getStrength", + args = {} + ) + public void test_getStrength() { + RuleBasedCollator collator; + try { + collator = new RuleBasedCollator("; \u0300 < a, A < b < c < d"); + } catch (ParseException e) { + fail("ParseException"); + return; + } + assertTrue("Wrong default", collator.getStrength() == Collator.TERTIARY); + } + + /** + * @tests java.text.Collator#setDecomposition(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "setDecomposition", + args = {int.class} + ) + @KnownFailure("Already fixed?") + public void test_setDecompositionI() { + Collator c = Collator.getInstance(Locale.FRENCH); + c.setStrength(Collator.IDENTICAL); + c.setDecomposition(Collator.NO_DECOMPOSITION); + assertTrue("Collator should not be using decomposition", !c.equals( + "\u212B", "\u00C5")); // "ANGSTROM SIGN" and "LATIN CAPITAL + // LETTER A WITH RING ABOVE" + c.setDecomposition(Collator.CANONICAL_DECOMPOSITION); + assertTrue("Collator should be using decomposition", c.equals("\u212B", + "\u00C5")); // "ANGSTROM SIGN" and "LATIN CAPITAL LETTER A WITH + // RING ABOVE" + // BEGIN android-removed + // Android doesn't support FULL_DECOMPOSITION + // c.setDecomposition(Collator.FULL_DECOMPOSITION); + // assertTrue("Should be equal under full decomposition", c.equals( + // "\u2163", "IV")); // roman number "IV" + // END android-removed + + try { + c.setDecomposition(-1); + fail("IllegalArgumentException should be thrown."); + } catch(IllegalArgumentException iae) { + //expected + } + } + + /** + * @tests java.text.Collator#setStrength(int) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Verifies IllegalArgumentException.", + method = "setStrength", + args = {int.class} + ) + public void test_setStrengthI() { + // Functionality is verified in compare and equals tests. + Collator collator = Collator.getInstance(); + collator.setStrength(Collator.PRIMARY); + assertEquals(Collator.PRIMARY, collator.getStrength()); + + collator.setStrength(Collator.SECONDARY); + assertEquals(Collator.SECONDARY, collator.getStrength()); + + collator.setStrength(Collator.TERTIARY); + assertEquals(Collator.TERTIARY, collator.getStrength()); + + collator.setStrength(Collator.IDENTICAL); + assertEquals(Collator.IDENTICAL, collator.getStrength()); + + try { + collator.setStrength(-1); + fail("IllegalArgumentException was not thrown."); + } catch(IllegalArgumentException iae) { + //expected + } + } + // Regression test for Android bug + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test.", + method = "setStrength", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test.", + method = "getCollationKey", + args = {java.lang.String.class} + ) + }) + public void test_stackCorruption() { + Collator mColl = Collator.getInstance(); + mColl.setStrength(Collator.PRIMARY); + mColl.getCollationKey("2d294f2d3739433565147655394f3762f3147312d3731641452f310"); + } + + // Test to verify that very large collation keys are not truncated. + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Doesn't verify null as a parameter.", + method = "getCollationKey", + args = {java.lang.String.class} + ) + public void test_collationKeySize() { + StringBuilder b = new StringBuilder(); + for (int i = 0; i < 1024; i++) { + b.append("0123456789ABCDEF"); + } + String sixteen = b.toString(); + b.append("_THE_END"); + String sixteenplus = b.toString(); + + Collator mColl = Collator.getInstance(); + mColl.setStrength(Collator.PRIMARY); + + try { + byte [] arr = mColl.getCollationKey(sixteen).toByteArray(); + int len = arr.length; + assertTrue("Collation key not 0 terminated", arr[arr.length - 1] == 0); + len--; + String foo = new String(arr, 0, len, "iso8859-1"); + + arr = mColl.getCollationKey(sixteen).toByteArray(); + len = arr.length; + assertTrue("Collation key not 0 terminated", arr[arr.length - 1] == 0); + len--; + String bar = new String(arr, 0, len, "iso8859-1"); + + assertTrue("Collation keys should differ", foo.equals(bar)); + } catch (UnsupportedEncodingException ex) { + fail("UnsupportedEncodingException"); + } + } + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "", + method = "setDecomposition", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "", + method = "compare", + args = {java.lang.String.class, java.lang.String.class} + ) + }) + public void test_decompositionCompatibility() { + Collator myCollator = Collator.getInstance(); + myCollator.setDecomposition(Collator.NO_DECOMPOSITION); + assertFalse("Error: \u00e0\u0325 should not equal to a\u0325\u0300 " + + "without decomposition", + myCollator.compare("\u00e0\u0325", "a\u0325\u0300") == 0); + myCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION); + assertTrue("Error: \u00e0\u0325 should equal to a\u0325\u0300 " + + "with decomposition", + myCollator.compare("\u00e0\u0325", "a\u0325\u0300") == 0); + } + + class TestCollator extends Collator { + + @Override + public int compare(String source, String target) { + return 0; + } + + @Override + public CollationKey getCollationKey(String source) { + return null; + } + + @Override + public int hashCode() { + return 0; + } + + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/DataFormatFieldTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/DataFormatFieldTest.java new file mode 100644 index 0000000..adf78fc --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/DataFormatFieldTest.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.DateFormat; +import java.text.DateFormat.Field; +import java.util.Calendar; + +@TestTargetClass(DateFormat.Field.class) +public class DataFormatFieldTest extends TestCase { + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Field", + args = {java.lang.String.class, int.class} + ) + public void test_ConstructorLjava_lang_StringLjava_lang_String() { + // Regression for HARMONY-178 + MyField field = new MyField("day of month", Calendar.ERA); + + assertEquals("field has wrong name", "day of month", field.getName()); + assertEquals("field has wrong Calendar field number", Calendar.ERA, + field.getCalendarField()); + + DateFormat.Field realField = DateFormat.Field + .ofCalendarField(Calendar.ERA); + assertSame("Modified calendar field with the same field number", + DateFormat.Field.ERA, realField); + + DateFormat.Field realField2 = DateFormat.Field + .ofCalendarField(Calendar.DAY_OF_MONTH); + assertSame("Modified calendar field with the same field number", + DateFormat.Field.DAY_OF_MONTH, realField2); + } + + static class MyField extends DateFormat.Field { + private static final long serialVersionUID = 1L; + + protected MyField(String fieldName, int calendarField) { + super(fieldName, calendarField); + } + + protected String getName() { + return super.getName(); + } + } + + /** + * @tests java.text.DateFormat$Field#Field(java.lang.String, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Field", + args = {java.lang.String.class, int.class} + ) + public void test_ConstructorLjava_lang_StringI() { + MyField field = new MyField("a field", Calendar.DAY_OF_WEEK); + + assertEquals("field has wrong name", "a field", field.getName()); + assertEquals("field has wrong Calendar field number", + Calendar.DAY_OF_WEEK, field.getCalendarField()); + + DateFormat.Field realField = DateFormat.Field + .ofCalendarField(Calendar.DAY_OF_WEEK); + assertSame("Modified calendar field with the same field number", + DateFormat.Field.DAY_OF_WEEK, realField); + } + + /** + * @tests java.text.DateFormat$Field#Field(java.lang.String, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Field", + args = {java.lang.String.class, int.class} + ) + public void test_Constructor2() { + MyField field = new MyField("day of month", Calendar.ERA); + + assertEquals("field has wrong name", "day of month", field.getName()); + assertEquals("field has wrong Calendar field number", Calendar.ERA, + field.getCalendarField()); + + DateFormat.Field realField = DateFormat.Field + .ofCalendarField(Calendar.ERA); + assertSame("Modified calendar field with the same field number", + DateFormat.Field.ERA, realField); + + DateFormat.Field realField2 = DateFormat.Field + .ofCalendarField(Calendar.DAY_OF_MONTH); + assertSame("Modified calendar field with the same field number", + DateFormat.Field.DAY_OF_MONTH, realField2); + } + + /** + * @tests java.text.DateFormat$Field#getCalendarField() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCalendarField", + args = {} + ) + public void test_getCalendarField() { + // Test for method int getCalendarField() + assertEquals("Field.AM_PM.getCalendarField() returned the wrong value", + Calendar.AM_PM, Field.AM_PM.getCalendarField()); + + // test special cases + assertEquals( + "Field.TIME_ZONE.getCalendarField() returned the wrong value", + -1, Field.TIME_ZONE.getCalendarField()); + assertEquals("Field.HOUR0.getCalendarField() returned the wrong value", + Calendar.HOUR, Field.HOUR0.getCalendarField()); + assertEquals("Field.HOUR1.getCalendarField() returned the wrong value", + -1, Field.HOUR1.getCalendarField()); + assertEquals( + "Field.HOUR_OF_DAY0.getCalendarField() returned the wrong value", + Calendar.HOUR_OF_DAY, Field.HOUR_OF_DAY0.getCalendarField()); + assertEquals( + "Field.HOUR_OF_DAY1.getCalendarField() returned the wrong value", + -1, Field.HOUR_OF_DAY1.getCalendarField()); + } + + /** + * @tests java.text.DateFormat$Field#ofCalendarField(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "ofCalendarField", + args = {int.class} + ) + public void test_ofCalendarFieldI() { + // Test for method static java.text.DateFormat.Field + // ofCalendarField(int) + assertSame("ofCalendarField(Calendar.AM_PM) returned the wrong value", + Field.AM_PM, Field.ofCalendarField(Calendar.AM_PM)); + + // test special cases + assertSame("ofCalendarField(Calendar.HOUR) returned the wrong value", + Field.HOUR0, Field.ofCalendarField(Calendar.HOUR)); + assertSame( + "ofCalendarField(Calendar.HOUR_OF_DAY) returned the wrong value", + Field.HOUR_OF_DAY0, Field.ofCalendarField(Calendar.HOUR_OF_DAY)); + + // test illegal args + try { + DateFormat.Field.ofCalendarField(-1); + fail("Expected IllegalArgumentException for ofCalendarField(-1)"); + } catch (IllegalArgumentException e) { + } + + try { + DateFormat.Field.ofCalendarField(Calendar.FIELD_COUNT); + fail("Expected IllegalArgumentException for ofCalendarField(Calendar.FIELD_COUNT)"); + } catch (IllegalArgumentException e) { + } + + // test Calendar fields that do not have corresponding DateFormat Fields + assertNull( + "ofCalendarField(Calendar.DST_OFFSET) returned the wrong value", + DateFormat.Field.ofCalendarField(Calendar.DST_OFFSET)); + assertNull( + "ofCalendarField(Calendar.ZONE_OFFSET) returned the wrong value", + DateFormat.Field.ofCalendarField(Calendar.ZONE_OFFSET)); + } + + /** + * @tests java.text.DateFormat$Field#readResolve() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "readResolve", + args = {} + ) + public void test_readResolve() { + // test for method java.lang.Object readResolve() + + // see serialization stress tests: + // implemented in + // SerializationStressTest4.test_writeObject_NumberFormat_Field() + + ObjectOutputStream out = null; + ObjectInputStream in = null; + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + out = new ObjectOutputStream(bytes); + + DateFormat.Field dfield, dfield2; + MyField field; + + // a regular instance of DateFormat.Field + dfield = DateFormat.Field.MILLISECOND; + + // a subclass instance with null name + field = new MyField(null, Calendar.AM_PM); + + out.writeObject(dfield); + out.writeObject(field); + + in = new ObjectInputStream(new ByteArrayInputStream(bytes + .toByteArray())); + + try { + dfield2 = (Field) in.readObject(); + assertSame("resolved incorrectly", dfield, dfield2); + } catch (IllegalArgumentException e) { + fail("Unexpected IllegalArgumentException: " + e); + } + + try { + in.readObject(); + fail("Expected InvalidObjectException for subclass instance with null name"); + } catch (InvalidObjectException e) { + } + + } catch (IOException e) { + fail("unexpected IOException" + e); + } catch (ClassNotFoundException e) { + fail("unexpected ClassNotFoundException" + e); + } finally { + try { + if (out != null) + out.close(); + if (in != null) + in.close(); + } catch (IOException e) { + } + } + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/DateFormatSymbolsTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/DateFormatSymbolsTest.java new file mode 100644 index 0000000..4151dc8 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/DateFormatSymbolsTest.java @@ -0,0 +1,485 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import java.text.DateFormatSymbols; +import java.util.Arrays; +import java.util.Locale; + +@TestTargetClass(DateFormatSymbols.class) +public class DateFormatSymbolsTest extends junit.framework.TestCase { + + private DateFormatSymbols dfs; + + /** + * @tests java.text.DateFormatSymbols#DateFormatSymbols() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DateFormatSymbols", + args = {} + ) + public void test_Constructor() { + // Test for method java.text.DateFormatSymbols() + // Used in tests + try { + new DateFormatSymbols(); + } catch (Exception e) { + fail("Constructor failed."); + } + } + + /** + * @tests java.text.DateFormatSymbols#DateFormatSymbols(java.util.Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DateFormatSymbols", + args = {java.util.Locale.class} + ) + public void test_ConstructorLjava_util_Locale() { + // Test for method java.text.DateFormatSymbols(java.util.Locale) + try { + new DateFormatSymbols(new Locale("en", "us")); + } catch (Exception e) { + fail("Constructor failed."); + } + } + + /** + * @tests java.text.DateFormatSymbols#clone() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + // Test for method java.lang.Object java.text.DateFormatSymbols.clone() + DateFormatSymbols symbols = new DateFormatSymbols(); + DateFormatSymbols clone = (DateFormatSymbols) symbols.clone(); + assertTrue("Not equal", symbols.equals(clone)); + } + + /** + * @tests java.text.DateFormatSymbols#equals(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + // Test for method boolean + // java.text.DateFormatSymbols.equals(java.lang.Object) + assertTrue("Equal object returned true", dfs.equals(dfs.clone())); + dfs.setLocalPatternChars("KKKKKKKKK"); + assertTrue("Un-Equal objects returned false", !dfs + .equals(new DateFormatSymbols())); + } + + /** + * @tests java.text.DateFormatSymbols#getAmPmStrings() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getAmPmStrings", + args = {} + ) + public void test_getAmPmStrings() { + // Test for method java.lang.String [] + // java.text.DateFormatSymbols.getAmPmStrings() + String[] retVal = dfs.getAmPmStrings(); + String[] val = { "AM", "PM" }; + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Array values do not match", retVal[i].equals(val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#getEras() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getEras", + args = {} + ) + public void test_getEras() { + // Test for method java.lang.String [] + // java.text.DateFormatSymbols.getEras() + String[] retVal = dfs.getEras(); + String[] val = { "BC", "AD" }; + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Array values do not match", retVal[i].equals(val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#getLocalPatternChars() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getLocalPatternChars", + args = {} + ) + public void test_getLocalPatternChars() { + // Test for method java.lang.String + // java.text.DateFormatSymbols.getLocalPatternChars() + String retVal = dfs.getLocalPatternChars(); + + String val = "GyMdkHmsSEDFwWahKzZ"; + + assertTrue("Returned incorrect pattern string", retVal.equals(val)); + } + + /** + * @tests java.text.DateFormatSymbols#getMonths() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMonths", + args = {} + ) + public void test_getMonths() { + // Test for method java.lang.String [] + // java.text.DateFormatSymbols.getMonths() + String[] retVal = dfs.getMonths(); + String[] val = { "January", "February", "March", "April", "May", + "June", "July", "August", "September", "October", "November", + "December", "" }; + if (retVal.length != val.length) + fail("Returned wrong array: " + retVal.length); + for (int i = 0; i < val.length; i++) + assertTrue("Array values do not match", retVal[i].equals(val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#getShortMonths() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getShortMonths", + args = {} + ) + public void test_getShortMonths() { + // Test for method java.lang.String [] + // java.text.DateFormatSymbols.getShortMonths() + String[] retVal = dfs.getShortMonths(); + String[] val = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", + "Aug", "Sep", "Oct", "Nov", "Dec", "" }; + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Array values do not match", retVal[i].equals(val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#getShortWeekdays() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getShortWeekdays", + args = {} + ) + public void test_getShortWeekdays() { + // Test for method java.lang.String [] + // java.text.DateFormatSymbols.getShortWeekdays() + String[] retVal = dfs.getShortWeekdays(); + String[] val = { "", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Array values do not match", retVal[i].equals(val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#getWeekdays() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getWeekdays", + args = {} + ) + public void test_getWeekdays() { + // Test for method java.lang.String [] + // java.text.DateFormatSymbols.getWeekdays() + String[] retVal = dfs.getWeekdays(); + String[] val = { "", "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" }; + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Array values do not match", retVal[i].equals(val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#getZoneStrings() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getZoneStrings", + args = {} + ) + public void test_getZoneStrings() { + // Test for method java.lang.String [][] + // java.text.DateFormatSymbols.getZoneStrings() + String[][] val = { { "XX" }, { "YY" } }; + dfs.setZoneStrings(val); + String[][] retVal = dfs.getZoneStrings(); + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Failed to set strings", Arrays + .equals(retVal[i], val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#hashCode() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + // Test for method int java.text.DateFormatSymbols.hashCode() + int hc1 = dfs.hashCode(); + int hc2 = dfs.hashCode(); + assertTrue("hashCode() returned inconsistent number : " + hc1 + " - " + + hc2, hc1 == hc2); + + assertTrue("hashCode() returns different values for equal() objects", + dfs.hashCode() == dfs.clone().hashCode()); + } + + /** + * @tests java.text.DateFormatSymbols#setAmPmStrings(java.lang.String[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setAmPmStrings", + args = {java.lang.String[].class} + ) + public void test_setAmPmStrings$Ljava_lang_String() { + // Test for method void + // java.text.DateFormatSymbols.setAmPmStrings(java.lang.String []) + String[] val = { "XX", "YY" }; + dfs.setAmPmStrings(val); + String[] retVal = dfs.getAmPmStrings(); + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Failed to set strings", retVal[i].equals(val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#setEras(java.lang.String[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setEras", + args = {java.lang.String[].class} + ) + public void test_setEras$Ljava_lang_String() { + // Test for method void + // java.text.DateFormatSymbols.setEras(java.lang.String []) + String[] val = { "XX", "YY" }; + dfs.setEras(val); + String[] retVal = dfs.getEras(); + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Failed to set strings", retVal[i].equals(val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#setLocalPatternChars(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setLocalPatternChars", + args = {java.lang.String.class} + ) + public void test_setLocalPatternCharsLjava_lang_String() { + // Test for method void + // java.text.DateFormatSymbols.setLocalPatternChars(java.lang.String) + dfs.setLocalPatternChars("GyMZZkHmsSEHHFwWahKz"); + String retVal = dfs.getLocalPatternChars(); + String val = "GyMZZkHmsSEHHFwWahKz"; + assertTrue("Returned incorrect pattern string", retVal.equals(val)); + + try { + // Regression for HARMONY-466 + new DateFormatSymbols().setLocalPatternChars(null); + fail("NullPointerException expected"); + } catch (NullPointerException e) { + // expected + } + } + + /** + * @tests java.text.DateFormatSymbols#setMonths(java.lang.String[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMonths", + args = {java.lang.String[].class} + ) + public void test_setMonths$Ljava_lang_String() { + // Test for method void + // java.text.DateFormatSymbols.setMonths(java.lang.String []) + String[] val = { "XX", "YY" }; + dfs.setMonths(val); + String[] retVal = dfs.getMonths(); + assertTrue("Return is identical", retVal != dfs.getMonths()); + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Failed to set strings", retVal[i].equals(val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#setShortMonths(java.lang.String[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setShortMonths", + args = {java.lang.String[].class} + ) + public void test_setShortMonths$Ljava_lang_String() { + // Test for method void + // java.text.DateFormatSymbols.setShortMonths(java.lang.String []) + String[] val = { "XX", "YY" }; + dfs.setShortMonths(val); + String[] retVal = dfs.getShortMonths(); + assertTrue("Return is identical", retVal != dfs.getShortMonths()); + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Failed to set strings", retVal[i].equals(val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#setShortWeekdays(java.lang.String[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setShortWeekdays", + args = {java.lang.String[].class} + ) + public void test_setShortWeekdays$Ljava_lang_String() { + // Test for method void + // java.text.DateFormatSymbols.setShortWeekdays(java.lang.String []) + String[] val = { "XX", "YY" }; + dfs.setShortWeekdays(val); + String[] retVal = dfs.getShortWeekdays(); + assertTrue("Return is identical", retVal != dfs.getShortWeekdays()); + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Failed to set strings", retVal[i].equals(val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#setWeekdays(java.lang.String[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setWeekdays", + args = {java.lang.String[].class} + ) + public void test_setWeekdays$Ljava_lang_String() { + // Test for method void + // java.text.DateFormatSymbols.setWeekdays(java.lang.String []) + String[] val = { "XX", "YY" }; + dfs.setWeekdays(val); + String[] retVal = dfs.getWeekdays(); + assertTrue("Return is identical", retVal != dfs.getWeekdays()); + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Failed to set strings", retVal[i].equals(val[i])); + } + + /** + * @tests java.text.DateFormatSymbols#setZoneStrings(java.lang.String[][]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setZoneStrings", + args = {java.lang.String[][].class} + ) + public void test_setZoneStrings$$Ljava_lang_String() { + // Test for method void + // java.text.DateFormatSymbols.setZoneStrings(java.lang.String [][]) + String[][] val = { { "XX" }, { "YY" } }; + dfs.setZoneStrings(val); + String[][] retVal = dfs.getZoneStrings(); + assertTrue("get returns identical", retVal != dfs.getZoneStrings()); + assertTrue("get[0] returns identical", retVal[0] != dfs + .getZoneStrings()[0]); + assertTrue("get returned identical", retVal != val); + if (retVal.length != val.length) + fail("Returned wrong array"); + for (int i = 0; i < val.length; i++) + assertTrue("Failed to set strings: " + retVal[i], Arrays.equals( + retVal[i], val[i])); + } + + /** + * Sets up the fixture, for example, open a network connection. This method + * is called before a test is executed. + */ + protected void setUp() { + dfs = new DateFormatSymbols(new Locale("en", "us")); + } + + /** + * Tears down the fixture, for example, close a network connection. This + * method is called after a test is executed. + */ + protected void tearDown() { + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/DateFormatTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/DateFormatTest.java new file mode 100644 index 0000000..d4d1a20 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/DateFormatTest.java @@ -0,0 +1,1080 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import java.text.DateFormat; +import java.text.DateFormatSymbols; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +@TestTargetClass(DateFormat.class) +public class DateFormatTest extends junit.framework.TestCase { + + private class MockDateFormat extends DateFormat { + + private static final long serialVersionUID = 1L; + + public MockDateFormat() { + super(); + } + + @Override + public Date parse(String source, ParsePosition pos) { + // it is a fake + return null; + } + + @Override + public StringBuffer format(Date date, StringBuffer toAppendTo, + FieldPosition fieldPosition) { + // it is a fake + return null; + } + } + + /** + * @tests java.text.DateFormat#DateFormat() Test of method + * java.text.DateFormat#DateFormat(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DateFormat", + args = {} + ) + public void test_Constructor() { + try { + new MockDateFormat(); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DateFormat#equals(java.lang.Object obj) Test of + * java.text.DateFormat#equals(java.lang.Object obj). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + try { + DateFormat format = DateFormat.getInstance(); + DateFormat clone = (DateFormat) format.clone(); + assertTrue("Clone and parent are not equaled", format.equals(clone)); + assertTrue("Clone is equal to other object", !clone + .equals(DateFormat.getTimeInstance())); + format.setCalendar(Calendar.getInstance()); + assertTrue("Clone and parent are not equaled", format.equals(clone)); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DateFormat#format(java.util.Date) Test of method + * java.text.DateFormat#format(java.util.Date). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {java.util.Date.class} + ) + public void test_formatLjava_util_Date() { + try { + DateFormat format = DateFormat.getDateTimeInstance( + DateFormat.SHORT, DateFormat.SHORT, Locale.US); + Date current = new Date(); + String dtf = format.format(current); + SimpleDateFormat sdf = new SimpleDateFormat("M/d/yy h:mm a"); + assertTrue("Incorrect date format", sdf.format(current).equals(dtf)); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DateFormat#format(Object, StringBuffer, FieldPosition) + * Test of method java.text.DateFormat#format(Object, StringBuffer, + * FieldPosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {java.lang.Object.class, java.lang.StringBuffer.class, java.text.FieldPosition.class} + ) + public void test_formatLjava_lang_ObjectLjava_lang_StringBufferLjava_text_FieldPosition() { + try { + DateFormat format = DateFormat.getDateTimeInstance( + DateFormat.SHORT, DateFormat.SHORT, Locale.US); + Date current = new Date(); + StringBuffer toAppend = new StringBuffer(); + FieldPosition fp = new FieldPosition(DateFormat.YEAR_FIELD); + StringBuffer sb = format.format(current, toAppend, fp); + SimpleDateFormat sdf = new SimpleDateFormat("M/d/yy h:mm a"); + assertTrue("Incorrect date format", sdf.format(current).equals( + sb.toString())); + assertTrue("Incorrect beginIndex of filed position", fp + .getBeginIndex() == sb.lastIndexOf("/") + 1); + assertTrue("Incorrect endIndex of filed position", + fp.getEndIndex() == sb.lastIndexOf("/") + 3); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DateFormat#clone() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + DateFormat format = DateFormat.getInstance(); + DateFormat clone = (DateFormat) format.clone(); + assertTrue("Clone not equal", format.equals(clone)); + clone.getNumberFormat().setMinimumFractionDigits(123); + assertTrue("Clone shares NumberFormat", !format.equals(clone)); + } + + /** + * @tests java.text.DateFormat#getAvailableLocales() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getAvailableLocales", + args = {} + ) + public void test_getAvailableLocales() { + Locale[] locales = DateFormat.getAvailableLocales(); + assertTrue("No locales", locales.length > 0); + boolean english = false, german = false; + for (int i = locales.length; --i >= 0;) { + if (locales[i].equals(Locale.ENGLISH)) + english = true; + if (locales[i].equals(Locale.GERMAN)) + german = true; + DateFormat f1 = DateFormat.getDateTimeInstance(DateFormat.SHORT, + DateFormat.SHORT, locales[i]); + assertTrue("Doesn't work", + f1.format(new Date()).getClass() == String.class); + } + assertTrue("Missing locales", english && german); + } + + /** + * @tests java.text.DateFormat#getCalendar() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCalendar", + args = {} + ) + public void test_getCalendar() { + DateFormat format = DateFormat.getInstance(); + Calendar cal1 = format.getCalendar(); + Calendar cal2 = format.getCalendar(); + assertTrue("Calendars not identical", cal1 == cal2); + } + + /** + * @tests java.text.DateFormat#getDateInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getDateInstance", + args = {} + ) + public void test_getDateInstance() { + SimpleDateFormat f2 = (SimpleDateFormat) DateFormat.getDateInstance(); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default", f2.equals(DateFormat.getDateInstance( + DateFormat.DEFAULT, Locale.getDefault()))); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + } + + /** + * @tests java.text.DateFormat#getDateInstance(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getDateInstance", + args = {int.class} + ) + public void test_getDateInstanceI() { + assertTrue("Default not medium", + DateFormat.DEFAULT == DateFormat.MEDIUM); + + SimpleDateFormat f2 = (SimpleDateFormat) DateFormat + .getDateInstance(DateFormat.SHORT); + assertTrue("Wrong class1", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default1", f2.equals(DateFormat.getDateInstance( + DateFormat.SHORT, Locale.getDefault()))); + assertTrue("Wrong symbols1", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work1", + f2.format(new Date()).getClass() == String.class); + + f2 = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.MEDIUM); + assertTrue("Wrong class2", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default2", f2.equals(DateFormat.getDateInstance( + DateFormat.MEDIUM, Locale.getDefault()))); + assertTrue("Wrong symbols2", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work2", + f2.format(new Date()).getClass() == String.class); + + f2 = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.LONG); + assertTrue("Wrong class3", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default3", f2.equals(DateFormat.getDateInstance( + DateFormat.LONG, Locale.getDefault()))); + assertTrue("Wrong symbols3", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work3", + f2.format(new Date()).getClass() == String.class); + + f2 = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.FULL); + assertTrue("Wrong class4", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default4", f2.equals(DateFormat.getDateInstance( + DateFormat.FULL, Locale.getDefault()))); + assertTrue("Wrong symbols4", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work4", + f2.format(new Date()).getClass() == String.class); + + // regression test for HARMONY-940 + try { + DateFormat.getDateInstance(77); + fail("Should throw IAE"); + } catch (IllegalArgumentException iae) { + // expected + } + } + + /** + * @tests java.text.DateFormat#getDateInstance(int, java.util.Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getDateInstance", + args = {int.class, java.util.Locale.class} + ) + public void test_getDateInstanceILjava_util_Locale() { + SimpleDateFormat f2 = (SimpleDateFormat) DateFormat.getDateInstance( + DateFormat.SHORT, Locale.GERMAN); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols(Locale.GERMAN))); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + + f2 = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.MEDIUM, + Locale.GERMAN); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols(Locale.GERMAN))); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + + f2 = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.LONG, + Locale.GERMAN); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols(Locale.GERMAN))); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + + f2 = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.FULL, + Locale.GERMAN); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols(Locale.GERMAN))); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + + // regression test for HARMONY-940 + try { + DateFormat.getDateInstance(77, Locale.GERMAN); + fail("Should throw IAE"); + } catch (IllegalArgumentException iae) { + // expected + } + } + + /** + * @tests java.text.DateFormat#getDateTimeInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getDateTimeInstance", + args = {} + ) + public void test_getDateTimeInstance() { + SimpleDateFormat f2 = (SimpleDateFormat) DateFormat + .getDateTimeInstance(); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default", f2.equals(DateFormat.getDateTimeInstance( + DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.getDefault()))); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + } + + private void testDateTime(int dStyle, int tStyle) { + SimpleDateFormat f2 = (SimpleDateFormat) DateFormat + .getDateTimeInstance(dStyle, tStyle); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + SimpleDateFormat date = (SimpleDateFormat) DateFormat.getDateInstance( + dStyle, Locale.getDefault()); + SimpleDateFormat time = (SimpleDateFormat) DateFormat.getTimeInstance( + tStyle, Locale.getDefault()); + assertTrue("Wrong default", f2.toPattern().equals( + date.toPattern() + " " + time.toPattern())); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + } + + /** + * @tests java.text.DateFormat#getDateTimeInstance(int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getDateTimeInstance", + args = {int.class, int.class} + ) + public void test_getDateTimeInstanceII() { + testDateTime(DateFormat.SHORT, DateFormat.SHORT); + testDateTime(DateFormat.SHORT, DateFormat.MEDIUM); + testDateTime(DateFormat.SHORT, DateFormat.LONG); + testDateTime(DateFormat.SHORT, DateFormat.FULL); + + testDateTime(DateFormat.MEDIUM, DateFormat.SHORT); + testDateTime(DateFormat.MEDIUM, DateFormat.MEDIUM); + testDateTime(DateFormat.MEDIUM, DateFormat.LONG); + testDateTime(DateFormat.MEDIUM, DateFormat.FULL); + + testDateTime(DateFormat.LONG, DateFormat.SHORT); + testDateTime(DateFormat.LONG, DateFormat.MEDIUM); + testDateTime(DateFormat.LONG, DateFormat.LONG); + testDateTime(DateFormat.LONG, DateFormat.FULL); + + testDateTime(DateFormat.FULL, DateFormat.SHORT); + testDateTime(DateFormat.FULL, DateFormat.MEDIUM); + testDateTime(DateFormat.FULL, DateFormat.LONG); + testDateTime(DateFormat.FULL, DateFormat.FULL); + + // regression test for HARMONY-940 + try { + DateFormat.getDateTimeInstance(77, 66); + fail("Should throw IAE"); + } catch (IllegalArgumentException iae) { + // expected + } + } + + private void testDateTimeLocale(int dStyle, int tStyle) { + SimpleDateFormat f2 = (SimpleDateFormat) DateFormat + .getDateTimeInstance(dStyle, tStyle, Locale.GERMAN); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + SimpleDateFormat date = (SimpleDateFormat) DateFormat.getDateInstance( + dStyle, Locale.GERMAN); + SimpleDateFormat time = (SimpleDateFormat) DateFormat.getTimeInstance( + tStyle, Locale.GERMAN); + assertTrue("Wrong default", f2.toPattern().equals( + date.toPattern() + " " + time.toPattern())); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols(Locale.GERMAN))); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + } + + /** + * @tests java.text.DateFormat#getDateTimeInstance(int, int, + * java.util.Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getDateTimeInstance", + args = {int.class, int.class, java.util.Locale.class} + ) + public void test_getDateTimeInstanceIILjava_util_Locale() { + testDateTimeLocale(DateFormat.SHORT, DateFormat.SHORT); + testDateTimeLocale(DateFormat.SHORT, DateFormat.MEDIUM); + testDateTimeLocale(DateFormat.SHORT, DateFormat.LONG); + testDateTimeLocale(DateFormat.SHORT, DateFormat.FULL); + + testDateTimeLocale(DateFormat.MEDIUM, DateFormat.SHORT); + testDateTimeLocale(DateFormat.MEDIUM, DateFormat.MEDIUM); + testDateTimeLocale(DateFormat.MEDIUM, DateFormat.LONG); + testDateTimeLocale(DateFormat.MEDIUM, DateFormat.FULL); + + testDateTimeLocale(DateFormat.LONG, DateFormat.SHORT); + testDateTimeLocale(DateFormat.LONG, DateFormat.MEDIUM); + testDateTimeLocale(DateFormat.LONG, DateFormat.LONG); + testDateTimeLocale(DateFormat.LONG, DateFormat.FULL); + + testDateTimeLocale(DateFormat.FULL, DateFormat.SHORT); + testDateTimeLocale(DateFormat.FULL, DateFormat.MEDIUM); + testDateTimeLocale(DateFormat.FULL, DateFormat.LONG); + testDateTimeLocale(DateFormat.FULL, DateFormat.FULL); + + // regression test for HARMONY-940 + try { + DateFormat.getDateTimeInstance(77, 66, Locale.GERMAN); + fail("Should throw IAE"); + } catch (IllegalArgumentException iae) { + // expected + } + } + + /** + * @tests java.text.DateFormat#getInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getInstance", + args = {} + ) + public void test_getInstance() { + SimpleDateFormat f2 = (SimpleDateFormat) DateFormat.getInstance(); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default", f2.equals(DateFormat.getDateTimeInstance( + DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault()))); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + } + + /** + * @tests java.text.DateFormat#getNumberFormat() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getNumberFormat", + args = {} + ) + public void test_getNumberFormat() { + DateFormat format = DateFormat.getInstance(); + NumberFormat nf1 = format.getNumberFormat(); + NumberFormat nf2 = format.getNumberFormat(); + assertTrue("NumberFormats not identical", nf1 == nf2); + } + + /** + * @tests java.text.DateFormat#getTimeInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getTimeInstance", + args = {} + ) + public void test_getTimeInstance() { + SimpleDateFormat f2 = (SimpleDateFormat) DateFormat.getTimeInstance(); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default", f2.equals(DateFormat.getTimeInstance( + DateFormat.DEFAULT, Locale.getDefault()))); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + } + + /** + * @tests java.text.DateFormat#getTimeInstance(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getTimeInstance", + args = {int.class} + ) + public void test_getTimeInstanceI() { + SimpleDateFormat f2 = (SimpleDateFormat) DateFormat + .getTimeInstance(DateFormat.SHORT); + assertTrue("Wrong class1", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default1", f2.equals(DateFormat.getTimeInstance( + DateFormat.SHORT, Locale.getDefault()))); + assertTrue("Wrong symbols1", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work1", + f2.format(new Date()).getClass() == String.class); + + f2 = (SimpleDateFormat) DateFormat.getTimeInstance(DateFormat.MEDIUM); + assertTrue("Wrong class2", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default2", f2.equals(DateFormat.getTimeInstance( + DateFormat.MEDIUM, Locale.getDefault()))); + assertTrue("Wrong symbols2", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work2", + f2.format(new Date()).getClass() == String.class); + + f2 = (SimpleDateFormat) DateFormat.getTimeInstance(DateFormat.LONG); + assertTrue("Wrong class3", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default3", f2.equals(DateFormat.getTimeInstance( + DateFormat.LONG, Locale.getDefault()))); + assertTrue("Wrong symbols3", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work3", + f2.format(new Date()).getClass() == String.class); + + f2 = (SimpleDateFormat) DateFormat.getTimeInstance(DateFormat.FULL); + assertTrue("Wrong class4", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default4", f2.equals(DateFormat.getTimeInstance( + DateFormat.FULL, Locale.getDefault()))); + assertTrue("Wrong symbols4", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work4", + f2.format(new Date()).getClass() == String.class); + + // regression test for HARMONY-940 + try { + DateFormat.getTimeInstance(77); + fail("Should throw IAE"); + } catch (IllegalArgumentException iae) { + // expected + } + } + + /** + * @tests java.text.DateFormat#getTimeInstance(int, java.util.Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getTimeInstance", + args = {int.class, java.util.Locale.class} + ) + public void test_getTimeInstanceILjava_util_Locale() { + SimpleDateFormat f2 = (SimpleDateFormat) DateFormat.getTimeInstance( + DateFormat.SHORT, Locale.GERMAN); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols(Locale.GERMAN))); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + + f2 = (SimpleDateFormat) DateFormat.getTimeInstance(DateFormat.MEDIUM, + Locale.GERMAN); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols(Locale.GERMAN))); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + + f2 = (SimpleDateFormat) DateFormat.getTimeInstance(DateFormat.LONG, + Locale.GERMAN); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols(Locale.GERMAN))); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + + f2 = (SimpleDateFormat) DateFormat.getTimeInstance(DateFormat.FULL, + Locale.GERMAN); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols(Locale.GERMAN))); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + + try { + DateFormat.getTimeInstance(77, Locale.GERMAN); + fail("Should throw IAE"); + } catch (IllegalArgumentException iae) { + // expected + } + } + + /** + * @tests java.text.DateFormat#getTimeZone() Test of method + * java.text.DateFormat#getTimeZone(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getTimeZone", + args = {} + ) + public void test_getTimeZone() { + try { + DateFormat format = DateFormat.getInstance(); + TimeZone tz = format.getTimeZone(); + //if(1 == 1) + // throw new Exception(tz.getClass().getName()); + // We know we are not sun.util so: + // Redundant checking + //assertFalse("Incorrect zone info", tz.getClass().getName().equals( + // "sun.util.calendar.ZoneInfo")); + assertTrue("Incorrect time zone", tz.equals(format.getCalendar() + .getTimeZone())); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DateFormat#hashCode() Test of method + * java.text.DateFormat#hashCode(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + try { + DateFormat df1 = DateFormat.getInstance(); + DateFormat df2 = (DateFormat) df1.clone(); + assertTrue("Hash codes of clones are not equal", + df1.hashCode() == df2.hashCode()); + assertTrue("Hash codes of different objects are the same", df1 + .hashCode() != DateFormat.getDateInstance().hashCode()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DateFormat#isLenient() Test of method + * java.text.DateFormat#isLenient(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "isLenient", + args = {} + ) + public void test_isLenient() { + DateFormat df = DateFormat.getInstance(); + Calendar c = df.getCalendar(); + if (df.isLenient()) { + try { + c.set(Calendar.DAY_OF_MONTH, 32); + c.get(Calendar.DAY_OF_MONTH); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + c.setLenient(false); + try { + c.set(Calendar.DAY_OF_MONTH, 32); + c.get(Calendar.DAY_OF_MONTH); + fail("Expected IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + // expected + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } else { + try { + c.set(Calendar.DAY_OF_MONTH, 32); + c.get(Calendar.DAY_OF_MONTH); + fail("Expected IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + // expected + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + c.setLenient(true); + try { + c.set(Calendar.DAY_OF_MONTH, 32); + c.get(Calendar.DAY_OF_MONTH); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + } + + /** + * @tests java.text.DateFormat#setCalendar(java.util.Calendar) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setCalendar", + args = {java.util.Calendar.class} + ) + public void test_setCalendarLjava_util_Calendar() { + DateFormat format = DateFormat.getInstance(); + Calendar cal = Calendar.getInstance(); + format.setCalendar(cal); + assertTrue("Not identical Calendar", cal == format.getCalendar()); + } + + /** + * @tests java.text.DateFormat#setNumberFormat(java.text.NumberFormat) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setNumberFormat", + args = {java.text.NumberFormat.class} + ) + public void test_setNumberFormatLjava_text_NumberFormat() { + DateFormat format = DateFormat.getInstance(); + NumberFormat f1 = NumberFormat.getInstance(); + format.setNumberFormat(f1); + assertTrue("Not identical NumberFormat", f1 == format.getNumberFormat()); + } + + /** + * @tests java.text.DateFormat#parse(String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "parse", + args = {java.lang.String.class} + ) + public void test_parseLString() { + DateFormat format = DateFormat.getInstance(); + + try { + format.parse("not a Date"); + fail("should throw ParseException first"); + } catch (ParseException pe) { + assertNotNull(pe.getMessage()); + } + + Date current = new Date(); + + try { + Date date = format.parse(format.format(current).toString()); + assertEquals(current.getDate(), date.getDate()); + assertEquals(current.getDay(), date.getDay()); + assertEquals(current.getMonth(), date.getMonth()); + assertEquals(current.getYear(), date.getYear()); + assertEquals(current.getHours(), date.getHours()); + assertEquals(current.getMinutes(), date.getMinutes()); + assertEquals(0, date.getSeconds()); + } catch(ParseException pe) { + fail("ParseException was thrown for current Date."); + } + + try { + format.parse("27/08/1998"); + fail("ParseException was not thrown."); + } catch(ParseException pe) { + //expected + } + try { + format.parse("30/30/908 4:50, PDT"); + fail("ParseException was not thrown."); + } catch(ParseException pe) { + //expected + } + try { + format.parse("837039928046"); + fail("ParseException was not thrown."); + } catch(ParseException pe) { + //expected + } + + format = DateFormat.getDateInstance(); + try { + Date date = format.parse(format.format(current).toString()); + assertEquals(current.getDate(), date.getDate()); + assertEquals(current.getDay(), date.getDay()); + assertEquals(current.getMonth(), date.getMonth()); + assertEquals(current.getYear(), date.getYear()); + assertEquals(0, date.getHours()); + assertEquals(0, date.getMinutes()); + assertEquals(0, date.getSeconds()); + } catch(ParseException pe) { + fail("ParseException was thrown for current Date."); + } + + try { + format.parse("Jan 16 1970"); + fail("ParseException was not thrown."); + } catch(ParseException pe) { + //expected + } + + try { + format.parse("27/08/1998"); + fail("ParseException was not thrown."); + } catch(ParseException pe) { + //expected + } + + format = DateFormat.getDateInstance(DateFormat.LONG); + try { + Date date = format.parse(format.format(current).toString()); + assertEquals(current.getDate(), date.getDate()); + assertEquals(current.getDay(), date.getDay()); + assertEquals(current.getMonth(), date.getMonth()); + assertEquals(current.getYear(), date.getYear()); + assertEquals(0, date.getHours()); + assertEquals(0, date.getMinutes()); + assertEquals(0, date.getSeconds()); + } catch(ParseException pe) { + fail("ParseException was thrown for current Date."); + } + + format = DateFormat.getDateInstance(DateFormat.MEDIUM); + try { + Date date = format.parse(format.format(current).toString()); + assertEquals(current.getDate(), date.getDate()); + assertEquals(current.getDay(), date.getDay()); + assertEquals(current.getMonth(), date.getMonth()); + assertEquals(current.getYear(), date.getYear()); + assertEquals(0, date.getHours()); + assertEquals(0, date.getMinutes()); + assertEquals(0, date.getSeconds()); + } catch(ParseException pe) { + fail("ParseException was thrown for current Date."); + } + + format = DateFormat.getTimeInstance(); + try { + Date date = format.parse(format.format(current).toString()); + assertEquals(1, date.getDate()); + assertEquals(0, date.getMonth()); + assertEquals(70, date.getYear()); + assertEquals(current.getHours(), date.getHours()); + assertEquals(current.getMinutes(), date.getMinutes()); + } catch(ParseException pe) { + fail("ParseException was thrown for current Date."); + } + + try { + format.parse("8:58:44"); + fail("ParseException was not thrown."); + } catch(ParseException pe) { + //expected + } + + format = DateFormat.getDateTimeInstance(); + try { + Date date = format.parse(format.format(current).toString()); + assertEquals(current.getDate(), date.getDate()); + assertEquals(current.getDay(), date.getDay()); + assertEquals(current.getMonth(), date.getMonth()); + assertEquals(current.getYear(), date.getYear()); + assertEquals(current.getHours(), date.getHours()); + assertEquals(current.getMinutes(), date.getMinutes()); + } catch(ParseException pe) { + fail("ParseException was thrown for current Date."); + } + + try { + format.parse("January 31 1970 7:52:34 AM PST"); + fail("ParseException was not thrown."); + } catch(ParseException pe) { + //expected + } + + try { + format.parse("January 31 1970"); + fail("ParseException was not thrown."); + } catch(ParseException pe) { + //expected + } + + format = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL); + try { + Date date = format.parse(format.format(current).toString()); + assertEquals(current.getDate(), date.getDate()); + assertEquals(current.getDay(), date.getDay()); + assertEquals(current.getMonth(), date.getMonth()); + assertEquals(current.getYear(), date.getYear()); + assertEquals(current.getHours(), date.getHours()); + assertEquals(current.getMinutes(), date.getMinutes()); + } catch(ParseException pe) { + fail("ParseException was thrown for current Date."); + } + + try { + format.parse("January 16, 1970 8:03:52 PM CET"); + fail("ParseException was not thrown."); + } catch(ParseException pe) { + //expected + } + } + + /** + * @tests java.text.DateFormat#parseObject(String, ParsePosition) Test of + * method java.text.DateFormat#parseObject(String, ParsePosition). + * Case 1: Try to parse correct data string. Case 2: Try to parse + * partialy correct data string. Case 3: Try to use argument + * ParsePosition as null. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "parseObject", + args = {java.lang.String.class, java.text.ParsePosition.class} + ) + public void test_parseObjectLjava_lang_StringLjava_text_ParsePosition() { + DateFormat df = DateFormat.getInstance(); + try { + // case 1: Try to parse correct data string. + Date current = new Date(); + ParsePosition pp = new ParsePosition(0); + int parseIndex = pp.getIndex(); + Date result = (Date) df.parseObject(df.format(current), pp); + + assertEquals("Dates are different.", current.getDate(), result.getDate()); + assertEquals("Days are different.", current.getDay(), result.getDay()); + assertEquals("Months are different.", current.getMonth(), result.getMonth()); + assertEquals("Years are different.", current.getYear(), result.getYear()); + assertEquals("Hours are different", current.getHours(), result.getHours()); + assertEquals("Minutes are diffetrent,", current.getMinutes(), result.getMinutes()); + + assertTrue("Parse operation return null", result != null); + assertTrue("ParseIndex is incorrect", pp.getIndex() != parseIndex); + + // case 2: Try to parse partially correct data string. + pp.setIndex(0); + char[] cur = df.format(current).toCharArray(); + cur[cur.length / 2] = 'Z'; + String partialCorrect = new String(cur); + result = (Date) df.parseObject(partialCorrect, pp); + assertTrue("Parse operation return not-null", result == null); + assertTrue("ParseIndex is incorrect", pp.getIndex() == 0); + assertTrue("ParseErrorIndex is incorrect", + pp.getErrorIndex() == cur.length / 2); + + pp.setIndex(2); + char[] curDate = df.format(current).toCharArray(); + char [] newArray = new char[curDate.length + pp.getIndex()]; + for(int i = 0; i < curDate.length; i++) { + newArray[i + pp.getIndex()] = curDate[i]; + } + result = (Date) df.parseObject(new String(newArray), pp); + //assertEquals(current, result); + + assertEquals("Dates are different.", current.getDate(), result.getDate()); + assertEquals("Days are different.", current.getDay(), result.getDay()); + assertEquals("Months are different.", current.getMonth(), result.getMonth()); + assertEquals("Years are different.", current.getYear(), result.getYear()); + assertEquals("Hours are different", current.getHours(), result.getHours()); + assertEquals("Minutes are diffetrent,", current.getMinutes(), result.getMinutes()); + + // case 3: Try to use argument ParsePosition as null. + try { + df.parseObject(df.format(current), null); + fail("Expected NullPointerException was not thrown"); + } catch (NullPointerException e) { + // expected + } + + assertNull(df.parseObject("test", pp)); + + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DateFormat#setLenient(boolean) Test of method + * java.text.DateFormat#setLenient(boolean). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setLenient", + args = {boolean.class} + ) + public void test_setLenientZ() { + DateFormat df = DateFormat.getInstance(); + Calendar c = df.getCalendar(); + try { + c.setLenient(true); + try { + c.set(Calendar.DAY_OF_MONTH, 32); + c.get(Calendar.DAY_OF_MONTH); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + c.setLenient(false); + try { + c.set(Calendar.DAY_OF_MONTH, 32); + c.get(Calendar.DAY_OF_MONTH); + fail("Expected IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + // expected + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } catch (Exception e) { + fail("Uexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DateFormat#setTimeZone(TimeZone) Test of method + * java.text.DateFormat#setTimeZone(TimeZone). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setTimeZone", + args = {java.util.TimeZone.class} + ) + public void test_setTimeZoneLjava_util_TimeZone() { + try { + DateFormat format = DateFormat.getInstance(); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + format.setTimeZone(tz); + assertTrue("TimeZone is set incorrectly", tz.equals(format + .getTimeZone())); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/DecimalFormatSymbolsTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/DecimalFormatSymbolsTest.java new file mode 100644 index 0000000..2998eb6 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/DecimalFormatSymbolsTest.java @@ -0,0 +1,765 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.AndroidOnly; +import dalvik.annotation.BrokenTest; +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.Currency; +import java.util.Locale; + +@TestTargetClass(DecimalFormatSymbols.class) +public class DecimalFormatSymbolsTest extends TestCase { + + DecimalFormatSymbols dfs; + + DecimalFormatSymbols dfsUS; + + /** + * @tests java.text.DecimalFormatSymbols#DecimalFormatSymbols() Test of + * method java.text.DecimalFormatSymbols#DecimalFormatSymbols(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DecimalFormatSymbols", + args = {} + ) + public void test_Constructor() { + // Test for method java.text.DecimalFormatSymbols() + try { + new DecimalFormatSymbols(); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormatSymbols#DecimalFormatSymbols(java.util.Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DecimalFormatSymbols", + args = {java.util.Locale.class} + ) + public void test_ConstructorLjava_util_Locale() { + DecimalFormatSymbols dfs = new DecimalFormatSymbols(new Locale("en", + "us")); + assertEquals("Returned incorrect symbols", '%', dfs.getPercent()); + + try { + new DecimalFormatSymbols(null); + fail("NullPointerException was not thrown."); + } catch(NullPointerException npe) { + //expected + } + } + + /** + * @tests java.text.DecimalFormatSymbols#clone() Test of method + * java.text.DecimalFormatSymbols#clone(). Case 1: Compare of + * internal variables of cloned objects. Case 2: Compare of clones. + * Case 3: Change the content of the clone. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + try { + // case 1: Compare of internal variables of cloned objects + DecimalFormatSymbols fs = new DecimalFormatSymbols(Locale.US); + DecimalFormatSymbols fsc = (DecimalFormatSymbols) fs.clone(); + assertEquals(fs.getCurrency(), fsc.getCurrency()); + + // case 2: Compare of clones + fs = new DecimalFormatSymbols(); + DecimalFormatSymbols fsc2 = (DecimalFormatSymbols) (fs.clone()); + // make sure the objects are equal + assertTrue("Object's clone isn't equal!", fs.equals(fsc2)); + + // case 3: + // change the content of the clone and make sure it's not equal + // anymore + // verifies that it's data is now distinct from the original + fs.setNaN("not-a-number"); + assertTrue("Object's changed clone should not be equal!", !fs + .equals(fsc2)); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormatSymbols#equals(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + assertTrue("Equal objects returned false", dfs.equals(dfs.clone())); + dfs.setDigit('B'); + assertTrue("Un-Equal objects returned true", !dfs + .equals(new DecimalFormatSymbols())); + + } + + /** + * @tests java.text.DecimalFormatSymbols#getCurrency() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCurrency", + args = {} + ) + public void test_getCurrency() { + Currency currency = Currency.getInstance("USD"); + assertTrue("Returned incorrect currency", + dfsUS.getCurrency() == currency); + + // BEGIN android-changed + // use cs_CZ instead + //Currency currK = Currency.getInstance("KRW"); + Currency currC = Currency.getInstance("CZK"); + Currency currX = Currency.getInstance("XXX"); + Currency currE = Currency.getInstance("EUR"); + // Currency currF = Currency.getInstance("FRF"); + + DecimalFormatSymbols dfs1 = new DecimalFormatSymbols(new Locale("cs", + "CZ")); + assertTrue("Test1: Returned incorrect currency", + dfs1.getCurrency() == currC); + assertEquals("Test1: Returned incorrect currencySymbol", "K\u010d", dfs1 + .getCurrencySymbol()); + assertEquals("Test1: Returned incorrect intlCurrencySymbol", "CZK", + dfs1.getInternationalCurrencySymbol()); + + dfs1 = new DecimalFormatSymbols(new Locale("", "CZ")); + assertTrue("Test2: Returned incorrect currency", + dfs1.getCurrency() == currC); + assertEquals("Test2: Returned incorrect currencySymbol", "CZK", dfs1 + .getCurrencySymbol()); + assertEquals("Test2: Returned incorrect intlCurrencySymbol", "CZK", + dfs1.getInternationalCurrencySymbol()); + + dfs1 = new DecimalFormatSymbols(new Locale("cs", "")); + assertEquals("Test3: Returned incorrect currency", + currX, dfs1.getCurrency()); + assertEquals("Test3: Returned incorrect currencySymbol", "\u00a4", dfs1 + .getCurrencySymbol()); + assertEquals("Test3: Returned incorrect intlCurrencySymbol", "XXX", + dfs1.getInternationalCurrencySymbol()); + + dfs1 = new DecimalFormatSymbols(new Locale("de", "AT")); + // END android-changed + assertTrue("Test4: Returned incorrect currency", + dfs1.getCurrency() == currE); + assertEquals("Test4: Returned incorrect currencySymbol", "\u20ac", dfs1 + .getCurrencySymbol()); + assertEquals("Test4: Returned incorrect intlCurrencySymbol", "EUR", + dfs1.getInternationalCurrencySymbol()); + + // RI fails these tests since it doesn't have the PREEURO variant + // dfs1 = new DecimalFormatSymbols(new Locale("fr", "FR","PREEURO")); + // assertTrue("Test5: Returned incorrect currency", dfs1.getCurrency() + // == currF); + // assertTrue("Test5: Returned incorrect currencySymbol", + // dfs1.getCurrencySymbol().equals("F")); + // assertTrue("Test5: Returned incorrect intlCurrencySymbol", + // dfs1.getInternationalCurrencySymbol().equals("FRF")); + } + + /** + * @tests java.text.DecimalFormatSymbols#getCurrencySymbol() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCurrencySymbol", + args = {} + ) + public void test_getCurrencySymbol() { + assertEquals("Returned incorrect currencySymbol", "$", dfsUS + .getCurrencySymbol()); + } + + /** + * @tests java.text.DecimalFormatSymbols#getDecimalSeparator() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getDecimalSeparator", + args = {} + ) + public void test_getDecimalSeparator() { + dfs.setDecimalSeparator('*'); + assertEquals("Returned incorrect DecimalSeparator symbol", '*', dfs + .getDecimalSeparator()); + } + + /** + * @tests java.text.DecimalFormatSymbols#getDigit() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getDigit", + args = {} + ) + public void test_getDigit() { + dfs.setDigit('*'); + assertEquals("Returned incorrect Digit symbol", '*', dfs.getDigit()); + } + + /** + * @tests java.text.DecimalFormatSymbols#getGroupingSeparator() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getGroupingSeparator", + args = {} + ) + public void test_getGroupingSeparator() { + dfs.setGroupingSeparator('*'); + assertEquals("Returned incorrect GroupingSeparator symbol", '*', dfs + .getGroupingSeparator()); + } + + /** + * @tests java.text.DecimalFormatSymbols#getInfinity() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getInfinity", + args = {} + ) + public void test_getInfinity() { + dfs.setInfinity("&"); + assertTrue("Returned incorrect Infinity symbol", + dfs.getInfinity() == "&"); + } + + /** + * @tests java.text.DecimalFormatSymbols#getInternationalCurrencySymbol() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getInternationalCurrencySymbol", + args = {} + ) + public void test_getInternationalCurrencySymbol() { + assertEquals("Returned incorrect InternationalCurrencySymbol", "USD", + dfsUS.getInternationalCurrencySymbol()); + } + + /** + * @tests java.text.DecimalFormatSymbols#getMinusSign() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMinusSign", + args = {} + ) + public void test_getMinusSign() { + dfs.setMinusSign('&'); + assertEquals("Returned incorrect MinusSign symbol", '&', dfs + .getMinusSign()); + } + + /** + * @tests java.text.DecimalFormatSymbols#getMonetaryDecimalSeparator() Test + * of method + * java.text.DecimalFormatSymbols#getMonetaryDecimalSeparator(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMonetaryDecimalSeparator", + args = {} + ) + public void test_getMonetaryDecimalSeparator() { + try { + dfs.setMonetaryDecimalSeparator(','); + assertEquals("Returned incorrect MonetaryDecimalSeparator symbol", + ',', dfs.getMonetaryDecimalSeparator()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormatSymbols#getNaN() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getNaN", + args = {} + ) + public void test_getNaN() { + dfs.setNaN("NAN!!"); + assertEquals("Returned incorrect nan symbol", "NAN!!", dfs.getNaN()); + } + + /** + * @tests java.text.DecimalFormatSymbols#getPatternSeparator() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getPatternSeparator", + args = {} + ) + public void test_getPatternSeparator() { + dfs.setPatternSeparator('X'); + assertEquals("Returned incorrect PatternSeparator symbol", 'X', dfs + .getPatternSeparator()); + } + + /** + * @tests java.text.DecimalFormatSymbols#getPercent() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getPercent", + args = {} + ) + public void test_getPercent() { + dfs.setPercent('*'); + assertEquals("Returned incorrect Percent symbol", '*', dfs.getPercent()); + } + + /** + * @tests java.text.DecimalFormatSymbols#getPerMill() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getPerMill", + args = {} + ) + public void test_getPerMill() { + dfs.setPerMill('#'); + assertEquals("Returned incorrect PerMill symbol", '#', dfs.getPerMill()); + } + + /** + * @tests java.text.DecimalFormatSymbols#getZeroDigit() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getZeroDigit", + args = {} + ) + public void test_getZeroDigit() { + dfs.setZeroDigit('*'); + assertEquals("Returned incorrect ZeroDigit symbol", '*', dfs + .getZeroDigit()); + } + + /** + * @tests java.text.DecimalFormatSymbols#hashCode() Test of method + * java.text.DecimalFormatSymbols#hashCode(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + @AndroidOnly("Succeeds against Android.") + public void test_hashCode() { + try { + DecimalFormatSymbols dfs1 = new DecimalFormatSymbols(); + DecimalFormatSymbols dfs2 = (DecimalFormatSymbols) dfs1.clone(); + assertTrue("Hash codes of equal object are equal", dfs2 + .hashCode() == dfs1.hashCode()); + dfs1.setInfinity("infinity_infinity"); + // BEGIN android-changed + assertTrue("Hash codes of non-equal objects are equal", dfs2 + .hashCode() != dfs1.hashCode()); + // END android-changed + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormatSymbols#setCurrency(java.util.Currency) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setCurrency", + args = {java.util.Currency.class} + ) + public void test_setCurrencyLjava_util_Currency() { + Locale locale = Locale.CANADA; + DecimalFormatSymbols dfs = ((DecimalFormat) NumberFormat + .getCurrencyInstance(locale)).getDecimalFormatSymbols(); + + try { + dfs.setCurrency(null); + fail("Expected NullPointerException"); + } catch (NullPointerException e) { + } + + Currency currency = Currency.getInstance("JPY"); + dfs.setCurrency(currency); + + assertTrue("Returned incorrect currency", currency == dfs.getCurrency()); + assertTrue("Returned incorrect currency symbol", currency.getSymbol( + locale).equals(dfs.getCurrencySymbol())); + assertTrue("Returned incorrect international currency symbol", currency + .getCurrencyCode().equals(dfs.getInternationalCurrencySymbol())); + } + + /** + * @tests java.text.DecimalFormatSymbols#setCurrencySymbol(java.lang.String) + * Test of method + * java.text.DecimalFormatSymbols#setCurrencySymbol(java.lang.String). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setCurrencySymbol", + args = {java.lang.String.class} + ) + public void test_setCurrencySymbolLjava_lang_String() { + try { + dfs.setCurrencySymbol("$"); + assertEquals("Returned incorrect CurrencySymbol symbol", "$", dfs + .getCurrencySymbol()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormatSymbols#setDecimalSeparator(char) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setDecimalSeparator", + args = {char.class} + ) + public void test_setDecimalSeparatorC() { + dfs.setDecimalSeparator('*'); + assertEquals("Returned incorrect DecimalSeparator symbol", '*', dfs + .getDecimalSeparator()); + } + + /** + * @tests java.text.DecimalFormatSymbols#setDigit(char) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setDigit", + args = {char.class} + ) + public void test_setDigitC() { + dfs.setDigit('*'); + assertEquals("Returned incorrect Digit symbol", '*', dfs.getDigit()); + } + + /** + * @tests java.text.DecimalFormatSymbols#setGroupingSeparator(char) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setGroupingSeparator", + args = {char.class} + ) + public void test_setGroupingSeparatorC() { + dfs.setGroupingSeparator('*'); + assertEquals("Returned incorrect GroupingSeparator symbol", '*', dfs + .getGroupingSeparator()); + } + + /** + * @tests java.text.DecimalFormatSymbols#setInfinity(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setInfinity", + args = {java.lang.String.class} + ) + public void test_setInfinityLjava_lang_String() { + dfs.setInfinity("&"); + assertTrue("Returned incorrect Infinity symbol", + dfs.getInfinity() == "&"); + } + + /** + * @tests java.text.DecimalFormatSymbols#setInternationalCurrencySymbol(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setInternationalCurrencySymbol", + args = {java.lang.String.class} + ) + @KnownFailure("getCurrency() doesn't return null. Test passes on RI.") + public void test_setInternationalCurrencySymbolLjava_lang_String() { + Locale locale = Locale.CANADA; + DecimalFormatSymbols dfs = ((DecimalFormat) NumberFormat + .getCurrencyInstance(locale)).getDecimalFormatSymbols(); + Currency currency = Currency.getInstance("JPY"); + dfs.setInternationalCurrencySymbol(currency.getCurrencyCode()); + + assertTrue("Test1: Returned incorrect currency", currency == dfs + .getCurrency()); + assertTrue("Test1: Returned incorrect currency symbol", currency + .getSymbol(locale).equals(dfs.getCurrencySymbol())); + assertTrue("Test1: Returned incorrect international currency symbol", + currency.getCurrencyCode().equals( + dfs.getInternationalCurrencySymbol())); + + String symbol = dfs.getCurrencySymbol(); + dfs.setInternationalCurrencySymbol("bogus"); + assertNull("Test2: Returned incorrect currency", dfs.getCurrency()); + assertTrue("Test2: Returned incorrect currency symbol", dfs + .getCurrencySymbol().equals(symbol)); + assertEquals("Test2: Returned incorrect international currency symbol", + "bogus", dfs.getInternationalCurrencySymbol()); + } + + /** + * @tests java.text.DecimalFormatSymbols#setMinusSign(char) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMinusSign", + args = {char.class} + ) + public void test_setMinusSignC() { + dfs.setMinusSign('&'); + assertEquals("Returned incorrect MinusSign symbol", '&', dfs + .getMinusSign()); + } + + /** + * @tests java.text.DecimalFormatSymbols#setMonetaryDecimalSeparator(char) + * Test of method + * java.text.DecimalFormatSymbols#setMonetaryDecimalSeparator(char). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMonetaryDecimalSeparator", + args = {char.class} + ) + public void test_setMonetaryDecimalSeparatorC() { + try { + dfs.setMonetaryDecimalSeparator('#'); + assertEquals("Returned incorrect MonetaryDecimalSeparator symbol", + '#', dfs.getMonetaryDecimalSeparator()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormatSymbols#setNaN(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setNaN", + args = {java.lang.String.class} + ) + public void test_setNaNLjava_lang_String() { + dfs.setNaN("NAN!!"); + assertEquals("Returned incorrect nan symbol", "NAN!!", dfs.getNaN()); + } + + /** + * @tests java.text.DecimalFormatSymbols#setPatternSeparator(char) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setPatternSeparator", + args = {char.class} + ) + public void test_setPatternSeparatorC() { + dfs.setPatternSeparator('X'); + assertEquals("Returned incorrect PatternSeparator symbol", 'X', dfs + .getPatternSeparator()); + } + + /** + * @tests java.text.DecimalFormatSymbols#setPercent(char) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setPercent", + args = {char.class} + ) + public void test_setPercentC() { + dfs.setPercent('*'); + assertEquals("Returned incorrect Percent symbol", '*', dfs.getPercent()); + } + + /** + * @tests java.text.DecimalFormatSymbols#setPerMill(char) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setPerMill", + args = {char.class} + ) + public void test_setPerMillC() { + dfs.setPerMill('#'); + assertEquals("Returned incorrect PerMill symbol", '#', dfs.getPerMill()); + } + + /** + * @tests java.text.DecimalFormatSymbols#setZeroDigit(char) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setZeroDigit", + args = {char.class} + ) + public void test_setZeroDigitC() { + dfs.setZeroDigit('*'); + assertEquals("Set incorrect ZeroDigit symbol", '*', dfs.getZeroDigit()); + } + + /** + * Sets up the fixture, for example, open a network connection. This method + * is called before a test is executed. + */ + protected void setUp() { + dfs = new DecimalFormatSymbols(); + dfsUS = new DecimalFormatSymbols(new Locale("en", "us")); + } + + /** + * Tears down the fixture, for example, close a network connection. This + * method is called after a test is executed. + */ + protected void tearDown() { + } + + // Test serialization mechanism of DecimalFormatSymbols + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Checks serialization mechanism.", + method = "!SerializationSelf", + args = {} + ) + public void test_serialization() throws Exception { + DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.FRANCE); + Currency currency = symbols.getCurrency(); + assertNotNull(currency); + + // serialize + ByteArrayOutputStream byteOStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOStream = new ObjectOutputStream(byteOStream); + objectOStream.writeObject(symbols); + + // and deserialize + ObjectInputStream objectIStream = new ObjectInputStream( + new ByteArrayInputStream(byteOStream.toByteArray())); + DecimalFormatSymbols symbolsD = (DecimalFormatSymbols) objectIStream + .readObject(); + + // The associated currency will not persist + currency = symbolsD.getCurrency(); + assertNotNull(currency); + } + + // Use RI to write DecimalFormatSymbols out, use Harmony to read + // DecimalFormatSymbols in. The read symbol will be equal with those + // instantiated inside Harmony. + + // This assertion will not come into existence the other way around. This is + // probably caused by different serialization mechanism used by RI and + // Harmony. + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Make sure all fields have non default values.", + method = "!SerializationGolden", + args = {} + ) + @BrokenTest("Deserialized object is not equal to the original object." + + "Test passes on RI.") + public void test_RIHarmony_compatible() throws Exception { + ObjectInputStream i = null; + try { + DecimalFormatSymbols symbols = new DecimalFormatSymbols( + Locale.FRANCE); + i = new ObjectInputStream( + getClass() + .getClassLoader() + .getResourceAsStream( + "serialization/java/text/DecimalFormatSymbols.ser")); + DecimalFormatSymbols symbolsD = (DecimalFormatSymbols) i + .readObject(); + assertEquals(symbols, symbolsD); + } catch(NullPointerException e) { + assertNotNull("Failed to load /serialization/java/text/" + + "DecimalFormatSymbols.ser", i); + } finally { + try { + if (i != null) { + i.close(); + } + } catch (Exception e) { + // ignore + } + } + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/DecimalFormatTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/DecimalFormatTest.java new file mode 100644 index 0000000..4ff54d0 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/DecimalFormatTest.java @@ -0,0 +1,2546 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.AndroidOnly; +import dalvik.annotation.BrokenTest; +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargets; + +import junit.framework.TestCase; + +import tests.support.Support_BitSet; +import tests.support.Support_DecimalFormat; + +import java.io.ObjectInputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.AttributedCharacterIterator; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Currency; +import java.util.Locale; + + +@TestTargetClass(DecimalFormat.class) +public class DecimalFormatTest extends TestCase { + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test for AttributedCharacterIterator.", + method = "formatToCharacterIterator", + args = {java.lang.Object.class} + ) + public void testAttributedCharacterIterator() throws Exception { + // Regression for http://issues.apache.org/jira/browse/HARMONY-333 + AttributedCharacterIterator iterator = new DecimalFormat() + .formatToCharacterIterator(new Integer(1)); + assertNotNull(iterator); + assertFalse("attributes should exist", iterator.getAttributes() + .isEmpty()); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "", + method = "formatToCharacterIterator", + args = {java.lang.Object.class} + ) + @KnownFailure("formatting numbers with more than ~300 digits doesn't work." + + " Also the third test doesn't round the string like the RI does") + public void test_formatToCharacterIterator() throws Exception { + AttributedCharacterIterator iterator; + int[] runStarts; + int[] runLimits; + String result; + char current; + + iterator = new DecimalFormat() + .formatToCharacterIterator(new BigDecimal("1.23456789E1234")); + runStarts = new int[] {0, 0, 2, 3, 3, 3, 6, 7, 7, 7, 10, 11, 11, 11, 14}; + runLimits = new int[] {2, 2, 3, 6, 6, 6, 7, 10, 10, 10, 11, 14, 14, 14, 15}; + result = "12,345,678,900,"; // 000,000,000,000.... + current = iterator.current(); + for (int i = 0; i < runStarts.length; i++) { + assertEquals("wrong start @" + i, runStarts[i], iterator.getRunStart()); + assertEquals("wrong limit @" + i, runLimits[i], iterator.getRunLimit()); + assertEquals("wrong char @" + i, result.charAt(i), current); + current = iterator.next(); + } + assertEquals(0, iterator.getBeginIndex()); + assertEquals(1646, iterator.getEndIndex()); + + iterator = new DecimalFormat() + .formatToCharacterIterator(new BigDecimal("1.23456789E301")); + runStarts = new int[] {0, 0, 2, 3, 3, 3, 6, 7, 7, 7, 10, 11, 11, 11, 14}; + runLimits = new int[] {2, 2, 3, 6, 6, 6, 7, 10, 10, 10, 11, 14, 14, 14, 15}; + result = "12,345,678,900,"; // 000,000,000,000.... + current = iterator.current(); + for (int i = 0; i < runStarts.length; i++) { + assertEquals("wrong start @" + i, runStarts[i], iterator.getRunStart()); + assertEquals("wrong limit @" + i, runLimits[i], iterator.getRunLimit()); + assertEquals("wrong char @" + i, result.charAt(i), current); + current = iterator.next(); + } + assertEquals(0, iterator.getBeginIndex()); + assertEquals(402, iterator.getEndIndex()); + + iterator = new DecimalFormat() + .formatToCharacterIterator(new BigDecimal("1.2345678E4")); + runStarts = new int[] {0, 0, 2, 3, 3, 3, 6, 7, 7, 7}; + runLimits = new int[] {2, 2, 3, 6, 6, 6, 7, 10, 10, 10}; + result = "12,345.678"; + current = iterator.current(); + for (int i = 0; i < runStarts.length; i++) { + assertEquals("wrong start @" + i, runStarts[i], iterator.getRunStart()); + assertEquals("wrong limit @" + i, runLimits[i], iterator.getRunLimit()); + assertEquals("wrong char @" + i, result.charAt(i), current); + current = iterator.next(); + } + assertEquals(0, iterator.getBeginIndex()); + assertEquals(10, iterator.getEndIndex()); + + iterator = new DecimalFormat() + .formatToCharacterIterator(new BigInteger("123456789")); + runStarts = new int[] {0, 0, 0, 3, 4, 4, 4, 7, 8, 8, 8}; + runLimits = new int[] {3, 3, 3, 4, 7, 7, 7, 8, 11, 11, 11}; + result = "123,456,789"; + current = iterator.current(); + for (int i = 0; i < runStarts.length; i++) { + assertEquals("wrong start @" + i, runStarts[i], iterator.getRunStart()); + assertEquals("wrong limit @" + i, runLimits[i], iterator.getRunLimit()); + assertEquals("wrong char @" + i, result.charAt(i), current); + current = iterator.next(); + } + assertEquals(0, iterator.getBeginIndex()); + assertEquals(11, iterator.getEndIndex()); + } + + /* + * Test the getter and setter of parseBigDecimal and parseIntegerOnly and + * test the default value of them. + */ + @TestTargets({ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "isParseBigDecimal", + args = {} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setParseBigDecimal", + args = {boolean.class} + ) + }) + public void test_isParseBigDecimalLjava_lang_Boolean_isParseIntegerOnlyLjava_lang_Boolean() { + + // parseBigDecimal default to false + DecimalFormat form = (DecimalFormat) DecimalFormat + .getInstance(Locale.US); + assertFalse(form.isParseBigDecimal()); + form.setParseBigDecimal(true); + assertTrue(form.isParseBigDecimal()); + + try { + Number result = form.parse("123.123"); + assertEquals(new BigDecimal("123.123"), result); + } catch (ParseException e) { + fail("ParseException was thrown."); + } + + form.setParseBigDecimal(false); + assertFalse(form.isParseBigDecimal()); + + try { + Number result = form.parse("123.123"); + assertFalse(result instanceof BigDecimal); + } catch (ParseException e) { + fail("ParseException was thrown."); + } + + // parseIntegerOnly default to false + assertFalse(form.isParseIntegerOnly()); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "isParseIntegerOnly", + args = {} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setParseIntegerOnly", + args = {boolean.class} + ) + }) + public void test_isParseIntegerOnly() { + + DecimalFormat format = new DecimalFormat(); + assertFalse("Default value of isParseIntegerOnly is true", + format.isParseIntegerOnly()); + + format.setParseIntegerOnly(true); + assertTrue(format.isParseIntegerOnly()); + try { + Number result = format.parse("123.123"); + assertEquals(new Long("123"), result); + } catch (ParseException e) { + fail("ParseException was thrown."); + } + + format.setParseIntegerOnly(false); + assertFalse(format.isParseIntegerOnly()); + try { + Number result = format.parse("123.123"); + assertEquals(new Double("123.123"), result); + } catch (ParseException e) { + fail("ParseException was thrown."); + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "isGroupingUsed", + args = {} + ) + public void test_isGroupingUsed() { + String [] patterns = {"####.##", "######.######", "000000.000000", + "######.000000", "000000.######", " ###.###", "$#####.######", + "$$####.######"}; + + for(String pattern:patterns) { + DecimalFormat format = new DecimalFormat(pattern); + assertFalse(format.isGroupingUsed()); + } + + DecimalFormat format = new DecimalFormat("###,####"); + assertTrue(format.isGroupingUsed()); + } + + // Test the type of the returned object + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "parse", + args = {java.lang.String.class, java.text.ParsePosition.class} + ) + @KnownFailure("Something seems wrong with Android implementation, here!") + public void test_parseLjava_lang_String_Ljava_text_ParsePosition() { + DecimalFormat form = (DecimalFormat) DecimalFormat + .getInstance(Locale.US); + Number number = form.parse("23.1", new ParsePosition(0)); + assertTrue(number instanceof Double); + + // Test parsed object of type double when + // parseBigDecimal is set to true + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + number = form.parse("23.1", new ParsePosition(0)); + assertTrue(number instanceof Double); + + form.setParseBigDecimal(true); + number = form.parse("23.1", new ParsePosition(0)); + + assertTrue(number instanceof BigDecimal); + assertEquals(new BigDecimal("23.1"), number); + + // When parseIntegerOnly set to true, all float numbers will be parsed + // into Long. + // With the exception that, the value is out of the bound of Long or + // some special values such as NaN or Infinity. + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + form.setParseIntegerOnly(true); + number = form.parse("23.1f", new ParsePosition(0)); + + assertTrue(number instanceof Long); + + number = form.parse("23.0", new ParsePosition(0)); + assertTrue(number instanceof Long); + + number = form.parse("-0.0", new ParsePosition(0)); + assertTrue(number instanceof Long); + assertTrue(new Long(0).equals(number)); + + number = form.parse("-9,223,372,036,854,775,8080.00", + new ParsePosition(0)); + assertTrue(number instanceof Double); + + // Even if parseIntegerOnly is set to true, NaN will be parsed to Double + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + form.setParseIntegerOnly(true); + DecimalFormatSymbols symbols = new DecimalFormatSymbols(); + number = form.parse(symbols.getNaN(), new ParsePosition(0)); + assertTrue(number instanceof Double); + + // Even if parseIntegerOnly is set to true, Infinity will still be + // parsed to Double + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + form.setParseIntegerOnly(true); + symbols = new DecimalFormatSymbols(); + number = form.parse(symbols.getInfinity(), new ParsePosition(0)); + assertTrue(number instanceof Double); + + // ParseBigDecimal take precedence of parseBigInteger + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + form.setParseIntegerOnly(true); + form.setParseBigDecimal(true); + + number = form.parse("23.1f", new ParsePosition(0)); + + assertTrue(number instanceof BigDecimal); + + number = form.parse("23.0", new ParsePosition(0)); + assertTrue(number instanceof BigDecimal); + + number = form.parse("-9,223,372,036,854,775,8080.00", + new ParsePosition(0)); + assertFalse(number instanceof BigInteger); + assertTrue(number instanceof BigDecimal); + + final String doubleMax2 = "359,538,626,972,463,141,629,054,847,463,408," + + "713,596,141,135,051,689,993,197,834,953,606,314,521,560,057,077," + + "521,179,117,265,533,756,343,080,917,907,028,764,928,468,642,653," + + "778,928,365,536,935,093,407,075,033,972,099,821,153,102,564,152," + + "490,980,180,778,657,888,151,737,016,910,267,884,609,166,473,806," + + "445,896,331,617,118,664,246,696,549,595,652,408,289,446,337,476," + + "354,361,838,599,762,500,808,052,368,249,716,736"; + number = form.parse(doubleMax2, new ParsePosition(0)); + assertTrue(number instanceof BigDecimal); + BigDecimal result = (BigDecimal)number; + assertEquals(new BigDecimal(Double.MAX_VALUE).add(new BigDecimal( + Double.MAX_VALUE)), result); + + // Test whether the parsed object is of type float. (To be specific, + // they are of type Double) + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + + number = form.parse("23.1f", new ParsePosition(0)); + assertTrue(number instanceof Double); + + form.setParseBigDecimal(true); + number = form.parse("23.1f", new ParsePosition(0)); + assertTrue(number instanceof BigDecimal); + assertEquals(new BigDecimal("23.1"), number); + + // Integer will be parsed to Long, unless parseBigDecimal is set to true + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + + number = form.parse("123", new ParsePosition(0)); + assertTrue(number instanceof Long); + + form.setParseBigDecimal(true); + number = form.parse("123", new ParsePosition(0)); + assertTrue(number instanceof BigDecimal); + assertEquals(new BigDecimal("123"), number); + + // NaN will be parsed to Double, no matter parseBigDecimal set or not. + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + symbols = new DecimalFormatSymbols(); + number = form.parse(symbols.getNaN() + "", new ParsePosition(0)); + assertTrue(number instanceof Double); + + form.setParseBigDecimal(true); + number = form.parse(symbols.getNaN() + "", new ParsePosition(0)); + assertTrue(number instanceof Double); + + // Infinity will be parsed to Double, no matter parseBigDecimal set or + // not. + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + symbols = new DecimalFormatSymbols(); + + number = form.parse(symbols.getInfinity(), new ParsePosition(0)); + + assertTrue(number instanceof Double); + assertEquals("Infinity", number.toString()); + // When set bigDecimal to true, the result of parsing infinity + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + symbols = new DecimalFormatSymbols(); + form.setParseBigDecimal(true); + + number = form.parse(symbols.getInfinity(), new ParsePosition(0)); + assertTrue(number instanceof Double); + assertEquals("Infinity", number.toString()); + + // Negative infinity will be parsed to double no matter parseBigDecimal + // set or not + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + symbols = new DecimalFormatSymbols(); + + number = form.parse("-" + symbols.getInfinity(), new ParsePosition(0)); + + assertTrue(number instanceof Double); + assertEquals("-Infinity", number.toString()); + + // When set bigDecimal to true, the result of parsing minus infinity + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + symbols = new DecimalFormatSymbols(); + form.setParseBigDecimal(true); + + number = form.parse("-" + symbols.getInfinity(), new ParsePosition(0)); + + assertTrue(number instanceof Double); + assertEquals("-Infinity", number.toString()); + + // -0.0 will be parsed to different type according to the combination of + // parseBigDecimal and parseIntegerOnly + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + + // parseBigDecimal == true; + // parseIntegerOnly == false; + form.setParseBigDecimal(true); + number = form.parse("-0", new ParsePosition(0)); + assertTrue(number instanceof BigDecimal); + + number = form.parse("-0.0", new ParsePosition(0)); + assertTrue(number instanceof BigDecimal); + + // parseBigDecimal == false; + // parseIntegerOnly == true; + form.setParseBigDecimal(false); + form.setParseIntegerOnly(true); + number = form.parse("-0", new ParsePosition(0)); + + assertTrue(number instanceof Long); + + number = form.parse("-0.0", new ParsePosition(0)); + assertTrue(number instanceof Long); + + // parseBigDecimal == false; + // parseIntegerOnly == false; + form.setParseBigDecimal(false); + form.setParseIntegerOnly(false); + number = form.parse("-0", new ParsePosition(0)); + assertTrue(number instanceof Double); + + number = form.parse("-0.0", new ParsePosition(0)); + assertTrue(number instanceof Double); + + // parseBigDecimal == true; + // parseIntegerOnly == true; + // parseBigDecimal take precedence of parseBigInteger + form.setParseBigDecimal(true); + form.setParseIntegerOnly(true); + number = form.parse("-0", new ParsePosition(0)); + assertTrue(number instanceof BigDecimal); + + number = form.parse("-0.0", new ParsePosition(0)); + assertTrue(number instanceof BigDecimal); + + number = form.parse("12.4", new ParsePosition(0)); + assertTrue(number instanceof BigDecimal); + + // When parseBigDecimal is set to false, no matter how massive the + // mantissa part of a number is, the number will be parsed into Double + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + + number = form.parse("9,223,372,036,854,775,808.00", + new ParsePosition(0)); + + assertTrue(number instanceof Double); + assertEquals("9.223372036854776E18", number.toString()); + + number = form.parse("-9,223,372,036,854,775,8080.00", + new ParsePosition(0)); + assertTrue(number instanceof Double); + assertEquals("-9.223372036854776E19", number.toString()); + + // When parseBigDecimal is set to true, if mantissa part of number + // exceeds Long.MAX_VALUE, the number will be parsed into BigDecimal + + form = (DecimalFormat) DecimalFormat.getInstance(Locale.US); + + form.setParseBigDecimal(true); + number = form.parse("9,223,372,036,854,775,808.00", + new ParsePosition(0)); + + assertTrue(number instanceof BigDecimal); + + assertEquals(9.223372036854776E18, number.doubleValue(), 0); + + number = form.parse("-9,223,372,036,854,775,8080.00", + new ParsePosition(0)); + + assertTrue(number instanceof BigDecimal); + assertEquals(-9.223372036854776E19, number.doubleValue(), 0); + + // The minimum value of Long will be parsed to Long when parseBigDecimal + // is not set + + ParsePosition pos = new ParsePosition(0); + DecimalFormat df = new DecimalFormat(); + pos = new ParsePosition(0); + Number nb = df.parse("" + Long.MIN_VALUE, pos); + assertTrue(nb instanceof Long); + + // The maximum value of Long will be parsed to Long when parseBigDecimal + // is set + pos = new ParsePosition(0); + df = new DecimalFormat(); + pos = new ParsePosition(0); + nb = df.parse("" + Long.MAX_VALUE, pos); + assertTrue(nb instanceof Long); + + // When parsing invalid string( which is neither consist of digits nor + // NaN/Infinity), a null will be returned. + + pos = new ParsePosition(0); + df = new DecimalFormat(); + try { + nb = df.parse("invalid", pos); + assertNull(nb); + } catch (NullPointerException e) { + fail("Should not throw NPE"); + } + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMaximumFractionDigits", + args = {} + ) + public void test_getMaximumFractionDigits() { + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + DecimalFormat form = (DecimalFormat) nform; + + // getMaximumFractionDigits of NumberFormat default to 3 + // getMaximumFractionDigits of DecimalFormat default to 3 + assertEquals(3, nform.getMaximumFractionDigits()); + assertEquals(3, form.getMaximumFractionDigits()); + + // Greater than 340 (critical number used to distinguish + // BigInteger and BigDecimal) + nform.setMaximumFractionDigits(500); + assertEquals(500, nform.getMaximumFractionDigits()); + assertEquals(500, form.getMaximumFractionDigits()); + + form.setMaximumFractionDigits(500); + assertEquals(500, nform.getMaximumFractionDigits()); + assertEquals(500, form.getMaximumFractionDigits()); + + form.format(12.3); + assertEquals(500, nform.getMaximumFractionDigits()); + assertEquals(500, form.getMaximumFractionDigits()); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMinimumFractionDigits", + args = {} + ) + public void test_getMinimumFractionDigits() { + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + DecimalFormat form = (DecimalFormat) nform; + + // getMinimumFractionDigits from NumberFormat (default to 0) + // getMinimumFractionDigits from DecimalFormat (default to 0) + assertEquals(0, nform.getMinimumFractionDigits()); + assertEquals(0, form.getMinimumFractionDigits()); + + // Greater than 340 (critical number used to distinguish + // BigInteger and BigDecimal) + nform.setMinimumFractionDigits(500); + assertEquals(500, nform.getMinimumFractionDigits()); + assertEquals(500, form.getMinimumFractionDigits()); + + form.setMaximumFractionDigits(400); + assertEquals(400, nform.getMinimumFractionDigits()); + assertEquals(400, form.getMinimumFractionDigits()); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + method = "getMinimumIntegerDigits", + args = {} + ) + public void test_getMaximumIntegerDigits() { + final int maxIntDigit = 309; + + // When use default locale, in this case zh_CN + // the returned instance of NumberFormat is a DecimalFormat + DecimalFormat form = new DecimalFormat("00.###E0"); + assertEquals(2, form.getMaximumIntegerDigits()); + + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + form = null; + if (nform instanceof DecimalFormat) { + form = (DecimalFormat) nform; + } + + // Greater than 309 (critical number used to distinguish + // BigInteger and BigDecimal) + nform.setMaximumIntegerDigits(500); + assertEquals(500, nform.getMaximumIntegerDigits()); + assertEquals(500, form.getMaximumIntegerDigits()); + + form = new DecimalFormat("00.###E0"); + assertEquals(2, form.getMaximumIntegerDigits()); + + form.setMaximumIntegerDigits(500); + assertEquals(500, nform.getMaximumIntegerDigits()); + assertEquals(500, form.getMaximumIntegerDigits()); + form.format(12.3); + assertEquals(500, nform.getMaximumIntegerDigits()); + assertEquals(500, form.getMaximumIntegerDigits()); + + nform = DecimalFormat.getInstance(Locale.US); + form = null; + if (nform instanceof DecimalFormat) { + form = (DecimalFormat) nform; + } + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + method = "getMinimumIntegerDigits", + args = {} + ) + @AndroidOnly("Difference to RI") + public void test_getMaximumIntegerDigits_AndroidOnly() { + final int maxIntDigit = 309; + + // When use default locale, in this case zh_CN + // the returned instance of NumberFormat is a DecimalFormat + DecimalFormat form = new DecimalFormat("00.###E0"); + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + nform = DecimalFormat.getInstance(Locale.US); + form = null; + if (nform instanceof DecimalFormat) { + form = (DecimalFormat) nform; + } + // getMaximumIntegerDigits from NumberFormat default to 309 + // getMaximumIntegerDigits from DecimalFormat default to 309 + // the following 2 assertions will fail on RI implementation, since the + // implementation of ICU and RI are not identical. RI does not give + // DecimalFormat an initial bound about its maximumIntegerDigits + // (default to Integer.MAX_VALUE: 2147483647 ) + assertEquals(maxIntDigit, nform.getMaximumIntegerDigits()); + assertEquals(maxIntDigit, form.getMaximumIntegerDigits()); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + method = "getMinimumIntegerDigits", + args = {} + ) + @KnownFailure("Something seems wrong with Android implementation, here!") + public void test_getMaximumIntegerDigits_AndroidFailure() { + // regression test for HARMONY-878 + assertTrue(new DecimalFormat("0\t0").getMaximumIntegerDigits() > 0); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMinimumIntegerDigits", + args = {} + ) + public void test_getMinimumIntegerDigits() { + final int minIntDigit = 1; + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + DecimalFormat form = (DecimalFormat) nform; + + // getMaximumIntegerDigits from NumberFormat (default to 1) + // getMaximumIntegerDigits from DecimalFormat (default to 1) + assertEquals(minIntDigit, nform.getMinimumIntegerDigits()); + assertEquals(minIntDigit, form.getMinimumIntegerDigits()); + + // Greater than 309 (critical number used to distinguish + // BigInteger and BigDecimal) + nform.setMinimumIntegerDigits(500); + assertEquals(500, nform.getMinimumIntegerDigits()); + assertEquals(500, form.getMinimumIntegerDigits()); + + form.setMaximumIntegerDigits(400); + assertEquals(400, nform.getMinimumIntegerDigits()); + assertEquals(400, form.getMinimumIntegerDigits()); + + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {java.lang.Object.class, java.lang.StringBuffer.class, java.text.FieldPosition.class} + ) + public void test_formatLjava_lang_Obj_Ljava_StringBuffer_Ljava_text_FieldPosition() { + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + DecimalFormat form = (DecimalFormat) nform; + + // If Object(including null) is not of type Number, + // IllegalArgumentException will be thrown out + try { + form.format(new Object(), new StringBuffer(), new FieldPosition(0)); + fail("Should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + try { + form.format(null, new StringBuffer(), new FieldPosition(0)); + fail("Should throw IAE"); + } catch (IllegalArgumentException e) { + // expected + } + + // When StringBuffer == null || FieldPosition == null + // NullPointerException will be thrown out. + try { + form.format(new Double(1.9), null, new FieldPosition(0)); + fail("Should throw NPE"); + } catch (NullPointerException e) { + // expected + } + + try { + form.format(new Double(1.3), new StringBuffer(), null); + fail("Should throw NPE"); + } catch (NullPointerException e) { + // expected + } + + try { + form.format(new Double(1.4), null, null); + fail("Should throw NPE"); + } catch (NullPointerException e) { + // expected + } + + try { + form.format(new Object(), null, null); + fail("Should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + + FieldPosition pos; + StringBuffer out; + DecimalFormat format = (DecimalFormat) NumberFormat + .getInstance(Locale.US); + + // format maxLong + pos = new FieldPosition(0); + out = format.format(new Long(Long.MAX_VALUE), new StringBuffer(), pos); + assertTrue("Wrong result L1: " + out, out.toString().equals( + "9,223,372,036,854,775,807")); + + // format minLong + pos = new FieldPosition(0); + out = format.format(new Long(Long.MIN_VALUE), new StringBuffer(), pos); + assertTrue("Wrong result L2: " + out, out.toString().equals( + "-9,223,372,036,854,775,808")); + + // format maxLong of type BigInteger + pos = new FieldPosition(0); + out = format.format(new java.math.BigInteger(String + .valueOf(Long.MAX_VALUE)), new StringBuffer(), pos); + assertTrue("Wrong result BI1: " + out, out.toString().equals( + "9,223,372,036,854,775,807")); + + // format minLong of type BigInteger + pos = new FieldPosition(0); + out = format.format(new java.math.BigInteger(String + .valueOf(Long.MIN_VALUE)), new StringBuffer(), pos); + assertTrue("Wrong result BI2: " + out, out.toString().equals( + "-9,223,372,036,854,775,808")); + + // format maxLong + 1 + java.math.BigInteger big; + pos = new FieldPosition(0); + big = new java.math.BigInteger(String.valueOf(Long.MAX_VALUE)) + .add(new java.math.BigInteger("1")); + out = format.format(big, new StringBuffer(), pos); + assertTrue("Wrong result BI3: " + out, out.toString().equals( + "9,223,372,036,854,775,808")); + + // format minLong - 1 + pos = new FieldPosition(0); + big = new java.math.BigInteger(String.valueOf(Long.MIN_VALUE)) + .add(new java.math.BigInteger("-1")); + out = format.format(big, new StringBuffer(), pos); + assertTrue("Wrong result BI4: " + out, out.toString().equals( + "-9,223,372,036,854,775,809")); + + // format big decimal + pos = new FieldPosition(0); + out = format.format(new java.math.BigDecimal("51.348"), + new StringBuffer(), pos); + assertTrue("Wrong result BD1: " + out, out.toString().equals("51.348")); + + // format big decimal + pos = new FieldPosition(0); + out = format.format(new java.math.BigDecimal("51"), new StringBuffer(), + pos); + assertTrue("Wrong result BD2: " + out, out.toString().equals("51")); + + // format big decimal Double.MAX_VALUE * 2 + java.math.BigDecimal bigDecimal; + pos = new FieldPosition(0); + final String doubleMax2 = "359,538,626,972,463,141,629,054,847,463,408," + + "713,596,141,135,051,689,993,197,834,953,606,314,521,560,057,077," + + "521,179,117,265,533,756,343,080,917,907,028,764,928,468,642,653," + + "778,928,365,536,935,093,407,075,033,972,099,821,153,102,564,152," + + "490,980,180,778,657,888,151,737,016,910,267,884,609,166,473,806," + + "445,896,331,617,118,664,246,696,549,595,652,408,289,446,337,476," + + "354,361,838,599,762,500,808,052,368,249,716,736"; + bigDecimal = new BigDecimal(Double.MAX_VALUE).add(new BigDecimal( + Double.MAX_VALUE)); + out = format.format(bigDecimal, new StringBuffer(), pos); + assertTrue("Wrong result BDmax2: " + out, out.toString().equals( + doubleMax2)); + + // format big decimal Double.MIN_VALUE + Double.MIN_VALUE + // and Double.MIN_VALUE - Double.MIN_VALUE + pos = new FieldPosition(0); + + bigDecimal = new BigDecimal(Double.MIN_VALUE).add(new BigDecimal( + Double.MIN_VALUE)); + out = format.format(bigDecimal, new StringBuffer(), pos); + + bigDecimal = new BigDecimal(Float.MAX_VALUE).add(new BigDecimal( + Float.MAX_VALUE)); + out = format.format(bigDecimal, new StringBuffer(), pos); + final String BDFloatMax2 = "680,564,693,277,057,719,623,408,366,969,033,850,880"; + assertTrue("Wrong result BDFloatMax2: " + out, out.toString().equals( + BDFloatMax2)); + // format big decimal Float.MIN_VALUE + Float.MIN_VALUE + // and Float.MIN_VALUE - Float.MIN_VALUE + bigDecimal = new BigDecimal(Float.MIN_VALUE).add(new BigDecimal( + Float.MIN_VALUE)); + out = format.format(bigDecimal, new StringBuffer(), pos); + final String BDFloatMin2 = "0"; + + bigDecimal = new BigDecimal(Float.MIN_VALUE).subtract(new BigDecimal( + Float.MIN_VALUE)); + out = format.format(bigDecimal, new StringBuffer(), pos); + + assertTrue("Wrong result BDFloatMax2: " + out, out.toString().equals( + BDFloatMin2)); + + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMaximumFractionDigits", + args = {int.class} + ) + public void test_setMaximumFractionDigitsLjava_lang_Integer() { + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + DecimalFormat form = (DecimalFormat) nform; + + form.setMaximumFractionDigits(-2); + assertEquals(0, form.getMaximumFractionDigits()); + + form.setMaximumFractionDigits(341); + assertEquals(341, form.getMaximumFractionDigits()); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMinimumFractionDigits", + args = {int.class} + ) + public void test_setMinimumFractionDigitsLjava_lang_Integer() { + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + DecimalFormat form = (DecimalFormat) nform; + + form.setMinimumFractionDigits(-3); + assertEquals(0, form.getMinimumFractionDigits()); + + form.setMinimumFractionDigits(310); + assertEquals(310, form.getMinimumFractionDigits()); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMaximumIntegerDigits", + args = {int.class} + ) + public void test_setMaximumIntegerDigitsLjava_lang_Integer() { + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + DecimalFormat form = (DecimalFormat) nform; + + form.setMaximumIntegerDigits(-3); + assertEquals(0, form.getMaximumIntegerDigits()); + + form.setMaximumIntegerDigits(310); + assertEquals(310, form.getMaximumIntegerDigits()); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMinimumIntegerDigits", + args = {int.class} + ) + public void test_setMinimumIntegerDigitsLjava_lang_Integer() { + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + DecimalFormat form = (DecimalFormat) nform; + + form.setMinimumIntegerDigits(-3); + assertEquals(0, form.getMinimumIntegerDigits()); + + form.setMinimumIntegerDigits(310); + assertEquals(310, form.getMinimumIntegerDigits()); + } + + // When MaxFractionDigits is set first and less than MinFractionDigits, max + // will be changed to min value + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMinimumFractionDigits", + args = {int.class} + ) + public void test_setMinimumFactionDigitsLjava_lang_Integer_setMaximumFractionDigitsLjava_lang_Integer() { + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + DecimalFormat form = (DecimalFormat) nform; + + form.setMaximumFractionDigits(100); + form.setMinimumFractionDigits(200); + + assertEquals(200, form.getMaximumFractionDigits()); + assertEquals(200, form.getMinimumFractionDigits()); + + form.setMaximumIntegerDigits(100); + form.setMinimumIntegerDigits(200); + + assertEquals(200, form.getMaximumIntegerDigits()); + assertEquals(200, form.getMinimumIntegerDigits()); + } + + // When MinFractionDigits is set first and less than MaxFractionDigits, min + // will be changed to max value + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "When MinFractionDigits is set first and less than MaxFractionDigits, min will be changed to max value", + method = "setMaximumFractionDigits", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "When MinFractionDigits is set first and less than MaxFractionDigits, min will be changed to max value", + method = "setMinimumFractionDigits", + args = {int.class} + ) + }) + public void test_setMaximumFactionDigitsLjava_lang_Integer_setMinimumFractionDigitsLjava_lang_Integer() { + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + DecimalFormat form = (DecimalFormat) nform; + + form.setMinimumFractionDigits(200); + form.setMaximumFractionDigits(100); + + assertEquals(100, form.getMaximumFractionDigits()); + assertEquals(100, form.getMinimumFractionDigits()); + + form.setMinimumIntegerDigits(200); + form.setMaximumIntegerDigits(100); + + assertEquals(100, form.getMaximumIntegerDigits()); + assertEquals(100, form.getMinimumIntegerDigits()); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + DecimalFormat format = (DecimalFormat) DecimalFormat + .getInstance(Locale.US); + DecimalFormat cloned = (DecimalFormat) format.clone(); + cloned.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US)); + assertEquals(format, cloned); + + Currency c = Currency.getInstance(Locale.US); + cloned.setCurrency(c); + + assertEquals(format, cloned); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setPositivePrefix", + args = {java.lang.String.class} + ) + public void test_setPositivePrefixLjava_lang_String() { + DecimalFormat format = new DecimalFormat(); + assertEquals("", format.getPositivePrefix()); + + format.setPositivePrefix("PosPrf"); + assertEquals("PosPrf", format.getPositivePrefix()); + try { + assertTrue(format.parse("PosPrf123.45").doubleValue() == 123.45); + } catch(java.text.ParseException pe) { + fail("ParseException was thrown."); + } + + format.setPositivePrefix(""); + assertEquals("", format.getPositivePrefix()); + + format.setPositivePrefix(null); + assertNull(format.getPositivePrefix()); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setPositiveSuffix", + args = {java.lang.String.class} + ) + public void test_setPositiveSuffixLjava_lang_String() { + DecimalFormat format = new DecimalFormat(); + assertEquals("", format.getPositiveSuffix()); + + format.setPositiveSuffix("PosSfx"); + assertEquals("PosSfx", format.getPositiveSuffix()); + try { + assertTrue(format.parse("123.45PosSfx").doubleValue() == 123.45); + } catch(java.text.ParseException pe) { + fail("ParseException was thrown."); + } + + format.setPositiveSuffix(""); + assertEquals("", format.getPositiveSuffix()); + + format.setPositiveSuffix(null); + assertNull(format.getPositiveSuffix()); + } + @TestTargets({ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setNegativePrefix", + args = {java.lang.String.class} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getNegativePrefix", + args = {} + ) + }) + public void test_setNegativePrefixLjava_lang_String() { + DecimalFormat format = new DecimalFormat(); + assertEquals("-", format.getNegativePrefix()); + + format.setNegativePrefix("NegPrf"); + assertEquals("NegPrf", format.getNegativePrefix()); + try { + assertTrue(format.parse("NegPrf123.45").doubleValue() == -123.45); + } catch(java.text.ParseException pe) { + fail("ParseException was thrown."); + } + format.setNegativePrefix(""); + assertEquals("", format.getNegativePrefix()); + + format.setNegativePrefix(null); + assertNull(format.getNegativePrefix()); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setNegativeSuffix", + args = {java.lang.String.class} + ) + public void test_setNegativeSuffixLjava_lang_String() { + DecimalFormat format = new DecimalFormat(); + assertEquals("", format.getNegativeSuffix()); + + format.setNegativeSuffix("NegSfx"); + assertEquals("NegSfx", format.getNegativeSuffix()); + try { + assertTrue(format.parse("123.45NegPfx").doubleValue() == 123.45); + } catch(java.text.ParseException pe) { + fail("ParseException was thrown."); + } + + format.setNegativeSuffix(""); + assertEquals("", format.getNegativeSuffix()); + + format.setNegativeSuffix(null); + assertNull(format.getNegativeSuffix()); + } + + /** + * @tests java.text.DecimalFormat#toLocalizedPattern() Test of method + * java.text.DecimalFormat#toLocalizedPattern(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "toLocalizedPattern", + args = {} + ) + public void test_toLocalizedPattern() { + DecimalFormat format = new DecimalFormat(); + format.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US)); + try { + format.applyLocalizedPattern("#.#"); + assertEquals("Wrong pattern 1", "#0.#", format.toLocalizedPattern()); + format.applyLocalizedPattern("#."); + assertEquals("Wrong pattern 2", "#0.", format.toLocalizedPattern()); + format.applyLocalizedPattern("#"); + assertEquals("Wrong pattern 3", "#", format.toLocalizedPattern()); + format.applyLocalizedPattern(".#"); + assertEquals("Wrong pattern 4", "#.0", format.toLocalizedPattern()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormat#toPattern() Test of method + * java.text.DecimalFormat#toPattern(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "toPattern", + args = {} + ) + public void test_toPattern() { + DecimalFormat format = new DecimalFormat(); + try { + format.applyPattern("#.#"); + assertEquals("Wrong pattern 1", "#0.#", format.toPattern()); + format.applyPattern("#."); + assertEquals("Wrong pattern 2", "#0.", format.toPattern()); + format.applyPattern("#"); + assertEquals("Wrong pattern 3", "#", format.toPattern()); + format.applyPattern(".#"); + assertEquals("Wrong pattern 4", "#.0", format.toPattern()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setGroupingUsed", + args = {boolean.class} + ) + public void test_setGroupingUse() { + DecimalFormat format = new DecimalFormat(); + + StringBuffer buf = new StringBuffer(); + format.setGroupingUsed(false); + format.format(new Long(1970), buf, new FieldPosition(0)); + assertEquals("1970", buf.toString()); + assertFalse(format.isGroupingUsed()); + format.format(new Long(1970), buf, new FieldPosition(0)); + assertEquals("19701970", buf.toString()); + assertFalse(format.isGroupingUsed()); + + format.setGroupingUsed(true); + format.format(new Long(1970), buf, new FieldPosition(0)); + assertEquals("197019701,970", buf.toString()); + assertTrue(format.isGroupingUsed()); + } + + /** + * @tests java.text.DecimalFormat#DecimalFormat() Test of method + * java.text.DecimalFormat#DecimalFormat(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DecimalFormat", + args = {} + ) + public void test_Constructor() { + // Test for method java.text.DecimalFormat() + // the constructor form that specifies a pattern is equal to the form + // constructed with no pattern and applying that pattern using the + // applyPattern call + try { + DecimalFormat format1 = new DecimalFormat(); + format1.applyPattern("'$'1000.0000"); + DecimalFormat format2 = new DecimalFormat(); + format2.applyPattern("'$'1000.0000"); + assertTrue( + "Constructed format did not match applied format object", + format2.equals(format1)); + DecimalFormat format3 = new DecimalFormat("'$'1000.0000"); + assertTrue( + "Constructed format did not match applied format object", + format3.equals(format1)); + DecimalFormat format4 = new DecimalFormat("'$'8000.0000"); + assertTrue( + "Constructed format did not match applied format object", + !format4.equals(format1)); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormat#DecimalFormat(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DecimalFormat", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() { + // Test for method java.text.DecimalFormat(java.lang.String) + // the constructor form that specifies a pattern is equal to the form + // constructed with no pattern and applying that pattern using the + // applyPattern call + DecimalFormat format = new DecimalFormat("'$'0000.0000"); + DecimalFormat format1 = new DecimalFormat(); + format1.applyPattern("'$'0000.0000"); + assertTrue("Constructed format did not match applied format object", + format.equals(format1)); + + String [] patterns = {"####.##", "######.######", "000000.000000", + "######.000000", "000000.######", " ###.###", "$#####.######", + "$$####.######", "%#,##,###,####", "#,##0.00;(#,##0.00)"}; + + for(String str:patterns) { + new DecimalFormat(str); + } + + try { + new DecimalFormat(null); + fail("NullPointerException was thrown."); + } catch(NullPointerException npe){ + //expected + } + + String [] incPatterns = {"%#,##,###,####'", "#.##0.00"}; + for(String str:incPatterns) { + try { + new DecimalFormat(str); + fail("NullPointerException was thrown for pattern: " + str); + } catch(IllegalArgumentException iae){ + //expected + } + } + } + + /** + * @tests java.text.DecimalFormat#DecimalFormat(java.lang.String, + * java.text.DecimalFormatSymbols) Test of method + * java.text.DecimalFormat#DecimalFormat(java.lang.String, + * java.text.DecimalFormatSymbols). Case 1: Try to construct object + * using correct pattern and fromat symbols. Case 2: Try to construct + * object using null arguments. Case 3: Try to construct object using + * incorrect pattern. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DecimalFormat", + args = {java.lang.String.class, java.text.DecimalFormatSymbols.class} + ) + public void test_ConstructorLjava_lang_StringLjava_text_DecimalFormatSymbols() { + try { + // case 1: Try to construct object using correct pattern and fromat + // symbols. + DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.CANADA); + DecimalFormat format1 = new DecimalFormat("'$'1000.0000", dfs); + DecimalFormat format2 = new DecimalFormat(); + format2.applyPattern("'$'1000.0000"); + format2.setDecimalFormatSymbols(dfs); + assertTrue( + "Constructed format did not match applied format object", + format2.equals(format1)); + assertTrue( + "Constructed format did not match applied format object", + !format1.equals(new DecimalFormat("'$'1000.0000", + new DecimalFormatSymbols(Locale.CHINA)))); + + // case 2: Try to construct object using null arguments. + try { + new DecimalFormat("'$'1000.0000", null); + fail("Expected NullPointerException was not thrown"); + } catch (NullPointerException e) { + // expected + } + try { + new DecimalFormat(null, new DecimalFormatSymbols()); + fail("Expected NullPointerException was not thrown"); + } catch (NullPointerException e) { + // expected + } + try { + new DecimalFormat(null, null); + fail("Expected NullPointerException was not thrown"); + } catch (NullPointerException e) { + // expected + } + + // case 3: Try to construct object using incorrect pattern. + try { + new DecimalFormat("$'", new DecimalFormatSymbols()); + fail("Expected IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + // expected + } + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormat#applyLocalizedPattern(java.lang.String) + * Test of method + * java.text.DecimalFormat#applyLocalizedPattern(java.lang.String). + * Case 1: Try to apply correct variants of pattern. Case 2: Try to + * apply malformed patten. Case 3: Try to apply null patern. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "applyLocalizedPattern", + args = {java.lang.String.class} + ) + public void test_applyLocalizedPatternLjava_lang_String() { + DecimalFormat format = new DecimalFormat(); + try { + // case 1: Try to apply correct variants of pattern. + format.applyLocalizedPattern("#.#"); + assertEquals("Wrong pattern 1", "#0.#", format.toLocalizedPattern()); + format.applyLocalizedPattern("#."); + assertEquals("Wrong pattern 2", "#0.", format.toLocalizedPattern()); + format.applyLocalizedPattern("#"); + assertEquals("Wrong pattern 3", "#", format.toLocalizedPattern()); + format.applyLocalizedPattern(".#"); + assertEquals("Wrong pattern 4", "#.0", format.toLocalizedPattern()); + + // case 2: Try to apply malformed patten. + try { + format.applyLocalizedPattern("'#,#:#0.0#;(#)"); + fail("Expected IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + // expected + } + + // case 3: Try to apply null patern. + try { + format.applyLocalizedPattern((String) null); + fail("Expected NullPointerException was not thrown"); + } catch (NullPointerException e) { + // expected + } + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormat#applyPattern(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "applyPattern", + args = {java.lang.String.class} + ) + public void test_applyPatternLjava_lang_String() { + DecimalFormat format = new DecimalFormat("#.#"); + assertEquals("Wrong pattern 1", "#0.#", format.toPattern()); + format = new DecimalFormat("#."); + assertEquals("Wrong pattern 2", "#0.", format.toPattern()); + format = new DecimalFormat("#"); + assertEquals("Wrong pattern 3", "#", format.toPattern()); + format = new DecimalFormat(".#"); + assertEquals("Wrong pattern 4", "#.0", format.toPattern()); + + DecimalFormat decFormat = new DecimalFormat("#.#"); + + try { + decFormat.applyPattern(null); + fail("NullPointerException was not thrown."); + } catch(NullPointerException npe) { + //expected + } + + String [] incPatterns = {"%#,##,###,####'", "#.##0.00"}; + for(String str:incPatterns) { + try { + decFormat.applyPattern(str); + fail("IllegalArgumentException was not thrown for pattern: " + + str); + } catch(IllegalArgumentException iae) { + //expected + } + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "applyPattern", + args = {java.lang.String.class} + ) + @KnownFailure("Something seems wrong with Android implementation, here!") + public void test_applyPatternLjava_lang_String_AndroidFailure() { + DecimalFormat decFormat = new DecimalFormat("#.#"); + String [] patterns = {"####.##", "######.######", "000000.000000", + "######.000000", "000000.######", " ###.###", "$#####.######", + "$$####.######", "%#,##,###,####", "#,##0.00;(#,##0.00)", + "##.##-E"}; + + String [] expResult = {"#0.##", "#0.######", "#000000.000000", + "#.000000", "#000000.######", " #0.###", "$#0.######", + "$$#0.######", "%#,####", "#,##0.00;(#,##0.00)", + "#0.##-E"}; + + for (int i = 0; i < patterns.length; i++) { + decFormat.applyPattern(patterns[i]); + assertEquals("Failed to apply following pattern: " + patterns[i], + expResult[i], decFormat.toPattern()); + } + } + + /** + * @tests java.text.DecimalFormat#clone() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + DecimalFormat format = (DecimalFormat) DecimalFormat + .getInstance(Locale.US); + DecimalFormat cloned = (DecimalFormat) format.clone(); + assertEquals(cloned.getDecimalFormatSymbols(), format + .getDecimalFormatSymbols()); + + format = new DecimalFormat("'$'0000.0000"); + DecimalFormat format1 = (DecimalFormat) (format.clone()); + // make sure the objects are equal + assertTrue("Object's clone isn't equal!", format.equals(format1)); + // change the content of the clone and make sure it's not equal anymore + // verifies that it's data is now distinct from the original + format1.applyPattern("'$'0000.####"); + assertTrue("Object's changed clone should not be equal!", !format + .equals(format1)); + } + + private void compare(String testName, String format, String expected) { + assertTrue(testName + " got: " + format + " expected: " + expected, + format.equals(expected)); + } + + private boolean compare(int count, String format, String expected) { + boolean result = format.equals(expected); + if (!result) + System.out.println("Failure test: " + count + " got: " + format + + " expected: " + expected); + return result; + } + + /** + * @tests java.text.DecimalFormat#format(double, java.lang.StringBuffer, + * java.text.FieldPosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "format", + args = {double.class, java.lang.StringBuffer.class, java.text.FieldPosition.class} + ) + @KnownFailure("Something seems wrong with Android implementation, here!") + public void test_formatDLjava_lang_StringBufferLjava_text_FieldPosition() { + new Support_DecimalFormat( + "test_formatDLjava_lang_StringBufferLjava_text_FieldPosition") + .t_format_with_FieldPosition(); + + int failCount = 0; + Support_BitSet failures = new Support_BitSet(); + + final DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US); + + DecimalFormat df = new DecimalFormat("00.0#E0", dfs); + compare("00.0#E0: 0.0", df.format(0.0), "00.0E0"); + compare("00.0#E0: 1.0", df.format(1.0), "10.0E-1"); + compare("00.0#E0: 12.0", df.format(12.0), "12.0E0"); + compare("00.0#E0: 123.0", df.format(123.0), "12.3E1"); + compare("00.0#E0: 1234.0", df.format(1234.0), "12.34E2"); + compare("00.0#E0: 12346.0", df.format(12346.0), "12.35E3"); + compare("00.0#E0: 99999.0", df.format(99999.0), "10.0E4"); + compare("00.0#E0: 1.2", df.format(1.2), "12.0E-1"); + compare("00.0#E0: 12.3", df.format(12.3), "12.3E0"); + compare("00.0#E0: 123.4", df.format(123.4), "12.34E1"); + compare("00.0#E0: 1234.6", df.format(1234.6), "12.35E2"); + compare("00.0#E0: 9999.9", df.format(9999.9), "10.0E3"); + compare("00.0#E0: 0.1", df.format(0.1), "10.0E-2"); + compare("00.0#E0: 0.12", df.format(0.12), "12.0E-2"); + compare("00.0#E0: 0.123", df.format(0.123), "12.3E-2"); + compare("00.0#E0: 0.1234", df.format(0.1234), "12.34E-2"); + compare("00.0#E0: 0.12346", df.format(0.12346), "12.35E-2"); + compare("00.0#E0: 0.99999", df.format(0.99999), "10.0E-1"); + compare("00.0#E0: -0.0", df.format(-0.0), "-00.0E0"); + compare("00.0#E0: -1.0", df.format(-1.0), "-10.0E-1"); + compare("00.0#E0: -12.0", df.format(-12.0), "-12.0E0"); + compare("00.0#E0: -123.0", df.format(-123.0), "-12.3E1"); + compare("00.0#E0: -1234.0", df.format(-1234.0), "-12.34E2"); + compare("00.0#E0: -12346.0", df.format(-12346.0), "-12.35E3"); + compare("00.0#E0: -99999.0", df.format(-99999.0), "-10.0E4"); + + df = new DecimalFormat("##0.0E0", dfs); + compare("##0.0E0: -0.0", df.format(-0.0), "-0.0E0"); + compare("##0.0E0: 0.0", df.format(0.0), "0.0E0"); + compare("##0.0E0: 1.0", df.format(1.0), "1.0E0"); + compare("##0.0E0: 12.0", df.format(12.0), "12E0"); + compare("##0.0E0: 123.0", df.format(123.0), "123E0"); // Android fails, here! + compare("##0.0E0: 1234.0", df.format(1234.0), "1.234E3"); + compare("##0.0E0: 12346.0", df.format(12346.0), "12.35E3"); + // Fails in JDK 1.2.2 + if (!compare(failCount, df.format(99999.0), "100E3")) + failures.set(failCount); + failCount++; + compare("##0.0E0: 999999.0", df.format(999999.0), "1.0E6"); + + df = new DecimalFormat("#00.0##E0", dfs); + compare("#00.0##E0: 0.1", df.format(0.1), "100E-3"); + compare("#00.0##E0: 0.12", df.format(0.12), "120E-3"); + compare("#00.0##E0: 0.123", df.format(0.123), "123E-3"); + compare("#00.0##E0: 0.1234", df.format(0.1234), "123.4E-3"); + compare("#00.0##E0: 0.1234567", df.format(0.1234567), "123.457E-3"); + compare("#00.0##E0: 0.01", df.format(0.01), "10.0E-3"); + compare("#00.0##E0: 0.012", df.format(0.012), "12.0E-3"); + compare("#00.0##E0: 0.0123", df.format(0.0123), "12.3E-3"); + compare("#00.0##E0: 0.01234", df.format(0.01234), "12.34E-3"); + compare("#00.0##E0: 0.01234567", df.format(0.01234567), "12.3457E-3"); + compare("#00.0##E0: 0.001", df.format(0.001), "1.00E-3"); + compare("#00.0##E0: 0.0012", df.format(0.0012), "1.20E-3"); + compare("#00.0##E0: 0.00123", df.format(0.00123), "1.23E-3"); + compare("#00.0##E0: 0.001234", df.format(0.001234), "1.234E-3"); + compare("#00.0##E0: 0.001234567", df.format(0.001234567), "1.23457E-3"); + compare("#00.0##E0: 0.0001", df.format(0.0001), "100E-6"); + compare("#00.0##E0: 0.00012", df.format(0.00012), "120E-6"); + compare("#00.0##E0: 0.000123", df.format(0.000123), "123E-6"); + compare("#00.0##E0: 0.0001234", df.format(0.0001234), "123.4E-6"); + compare("#00.0##E0: 0.0001234567", df.format(0.0001234567), + "123.457E-6"); + + // Fails in JDK 1.2.2 + if (!compare(failCount, df.format(0.0), "0.00E0")) + failures.set(failCount); + failCount++; + compare("#00.0##E0: 1.0", df.format(1.0), "1.00E0"); + compare("#00.0##E0: 12.0", df.format(12.0), "12.0E0"); + compare("#00.0##E0: 123.0", df.format(123.0), "123E0"); + compare("#00.0##E0: 1234.0", df.format(1234.0), "1.234E3"); + compare("#00.0##E0: 12345.0", df.format(12345.0), "12.345E3"); + compare("#00.0##E0: 123456.0", df.format(123456.0), "123.456E3"); + compare("#00.0##E0: 1234567.0", df.format(1234567.0), "1.23457E6"); + compare("#00.0##E0: 12345678.0", df.format(12345678.0), "12.3457E6"); + compare("#00.0##E0: 99999999.0", df.format(99999999.0), "100E6"); + + df = new DecimalFormat("#.0E0", dfs); + compare("#.0E0: -0.0", df.format(-0.0), "-.0E0"); + compare("#.0E0: 0.0", df.format(0.0), ".0E0"); + compare("#.0E0: 1.0", df.format(1.0), ".1E1"); + compare("#.0E0: 12.0", df.format(12.0), ".12E2"); + compare("#.0E0: 123.0", df.format(123.0), ".12E3"); + compare("#.0E0: 1234.0", df.format(1234.0), ".12E4"); + compare("#.0E0: 9999.0", df.format(9999.0), ".1E5"); + + df = new DecimalFormat("0.#E0", dfs); + compare("0.#E0: -0.0", df.format(-0.0), "-0E0"); + compare("0.#E0: 0.0", df.format(0.0), "0E0"); + compare("0.#E0: 1.0", df.format(1.0), "1E0"); + compare("0.#E0: 12.0", df.format(12.0), "1.2E1"); + compare("0.#E0: 123.0", df.format(123.0), "1.2E2"); + compare("0.#E0: 1234.0", df.format(1234.0), "1.2E3"); + compare("0.#E0: 9999.0", df.format(9999.0), "1E4"); + + df = new DecimalFormat(".0E0", dfs); + compare(".0E0: -0.0", df.format(-0.0), "-.0E0"); + compare(".0E0: 0.0", df.format(0.0), ".0E0"); + compare(".0E0: 1.0", df.format(1.0), ".1E1"); + compare(".0E0: 12.0", df.format(12.0), ".1E2"); + compare(".0E0: 123.0", df.format(123.0), ".1E3"); + compare(".0E0: 1234.0", df.format(1234.0), ".1E4"); + compare(".0E0: 9999.0", df.format(9999.0), ".1E5"); + + df = new DecimalFormat("0.E0", dfs); + // Fails in JDK 1.2.2 + if (!compare(failCount, df.format(0.0), "0.E0")) + failures.set(failCount); + failCount++; + if (!compare(failCount, df.format(1.0), "1.E0")) + failures.set(failCount); + failCount++; + if (!compare(failCount, df.format(12.0), "1.E1")) + failures.set(failCount); + failCount++; + if (!compare(failCount, df.format(123.0), "1.E2")) + failures.set(failCount); + failCount++; + if (!compare(failCount, df.format(1234.0), "1.E3")) + failures.set(failCount); + failCount++; + if (!compare(failCount, df.format(9999.0), "1.E4")) + failures.set(failCount); + failCount++; + + df = new DecimalFormat("##0.00#E0", dfs); + compare("##0.00#E0: 0.1", df.format(0.1), "100E-3"); + compare("##0.00#E0: 0.1234567", df.format(0.1234567), "123.457E-3"); + compare("##0.00#E0: 0.9999999", df.format(0.9999999), "1.00E0"); + compare("##0.00#E0: 0.01", df.format(0.01), "10.0E-3"); + compare("##0.00#E0: 0.01234567", df.format(0.01234567), "12.3457E-3"); + compare("##0.00#E0: 0.09999999", df.format(0.09999999), "100E-3"); + compare("##0.00#E0: 0.001", df.format(0.001), "1.00E-3"); + compare("##0.00#E0: 0.001234567", df.format(0.001234567), "1.23457E-3"); + compare("##0.00#E0: 0.009999999", df.format(0.009999999), "10.0E-3"); + compare("##0.00#E0: 0.0001", df.format(0.0001), "100E-6"); + compare("##0.00#E0: 0.0001234567", df.format(0.0001234567), + "123.457E-6"); + compare("##0.00#E0: 0.0009999999", df.format(0.0009999999), "1.00E-3"); + + df = new DecimalFormat("###0.00#E0", dfs); + compare("###0.00#E0: 0.1", df.format(0.1), "1000E-4"); + compare("###0.00#E0: 0.12345678", df.format(0.12345678), "1234.568E-4"); + compare("###0.00#E0: 0.99999999", df.format(0.99999999), "1.00E0"); + compare("###0.00#E0: 0.01", df.format(0.01), "100E-4"); + compare("###0.00#E0: 0.012345678", df.format(0.012345678), + "123.4568E-4"); + compare("###0.00#E0: 0.099999999", df.format(0.099999999), "1000E-4"); + compare("###0.00#E0: 0.001", df.format(0.001), "10.0E-4"); + compare("###0.00#E0: 0.0012345678", df.format(0.0012345678), + "12.34568E-4"); + compare("###0.00#E0: 0.0099999999", df.format(0.0099999999), "100E-4"); + compare("###0.00#E0: 0.0001", df.format(0.0001), "1.00E-4"); + compare("###0.00#E0: 0.00012345678", df.format(0.00012345678), + "1.234568E-4"); + compare("###0.00#E0: 0.00099999999", df.format(0.00099999999), + "10.0E-4"); + // Fails in JDK 1.2.2 + if (!compare(failCount, df.format(0.00001), "1000E-8")) + failures.set(failCount); + failCount++; + compare("###0.00#E0: 0.000012345678", df.format(0.000012345678), + "1234.568E-8"); + compare("###0.00#E0: 0.000099999999", df.format(0.000099999999), + "1.00E-4"); + + df = new DecimalFormat("###0.0#E0", dfs); + compare("###0.0#E0: 0.1", df.format(0.1), "1000E-4"); + compare("###0.0#E0: 0.1234567", df.format(0.1234567), "1234.57E-4"); + compare("###0.0#E0: 0.9999999", df.format(0.9999999), "1.0E0"); + // Fails in JDK 1.2.2 + if (!compare(failCount, df.format(0.01), "100E-4")) + failures.set(failCount); + failCount++; + compare("###0.0#E0: 0.01234567", df.format(0.01234567), "123.457E-4"); + compare("###0.0#E0: 0.09999999", df.format(0.09999999), "1000E-4"); + compare("###0.0#E0: 0.001", df.format(0.001), "10E-4"); + compare("###0.0#E0: 0.001234567", df.format(0.001234567), "12.3457E-4"); + // Fails in JDK 1.2.2 + if (!compare(failCount, df.format(0.009999999), "100E-4")) + failures.set(failCount); + failCount++; + compare("###0.0#E0: 0.0001", df.format(0.0001), "1.0E-4"); + compare("###0.0#E0: 0.0001234567", df.format(0.0001234567), + "1.23457E-4"); + compare("###0.0#E0: 0.0009999999", df.format(0.0009999999), "10E-4"); + // Fails in JDK 1.2.2 + if (!compare(failCount, df.format(0.00001), "1000E-8")) + failures.set(failCount); + failCount++; + compare("###0.0#E0: 0.00001234567", df.format(0.00001234567), + "1234.57E-8"); + compare("###0.0#E0: 0.00009999999", df.format(0.00009999999), "1.0E-4"); + + assertTrue("Failed " + failures + " of " + failCount, + failures.length() == 0); + + String formatString = "##0.#"; + df = new DecimalFormat(formatString, dfs); + df.setMinimumFractionDigits(30); + compare(formatString + ": 0.000000000000000000000000000000", df + .format(0.0), "0.000000000000000000000000000000"); + compare(formatString + ": -0.000000000000000000000000000000", df + .format(-0.0), "-0.000000000000000000000000000000"); + compare(formatString + ": 1.000000000000000000000000000000", df + .format(1.0), "1.000000000000000000000000000000"); + compare(formatString + ": -1.000000000000000000000000000000", df + .format(-1.0), "-1.000000000000000000000000000000"); + + df = new DecimalFormat(formatString); + df.setMaximumFractionDigits(30); + compare(formatString + ": 0", df.format(0.0), "0"); + compare(formatString + ": -0", df.format(-0.0), "-0"); + compare(formatString + ": 1", df.format(1.0), "1"); + compare(formatString + ": -1", df.format(-1.0), "-1"); + } + + /** + * @tests java.text.DecimalFormat#format(long, java.lang.StringBuffer, + * java.text.FieldPosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "format", + args = {long.class, java.lang.StringBuffer.class, java.text.FieldPosition.class} + ) + @KnownFailure("Something seems wrong with Android implementation, here!") + public void test_formatJLjava_lang_StringBufferLjava_text_FieldPosition() { + int failCount = 0; + Support_BitSet failures = new Support_BitSet(); + + final DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US); + + DecimalFormat df = new DecimalFormat("00.0#E0", dfs); + assertEquals("00.0#E0: 0", "00.0E0", df.format(0)); + assertEquals("00.0#E0: 1", "10.0E-1", df.format(1)); + assertEquals("00.0#E0: 12", "12.0E0", df.format(12)); + assertEquals("00.0#E0: 123", "12.3E1", df.format(123)); + assertEquals("00.0#E0: 1234", "12.34E2", df.format(1234)); + assertEquals("00.0#E0: 12346", "12.35E3", df.format(12346)); + assertEquals("00.0#E0: 99999", "10.0E4", df.format(99999)); + assertEquals("00.0#E0: -1", "-10.0E-1", df.format(-1)); + assertEquals("00.0#E0: -12", "-12.0E0", df.format(-12)); + assertEquals("00.0#E0: -123", "-12.3E1", df.format(-123)); + assertEquals("00.0#E0: -1234", "-12.34E2", df.format(-1234)); + assertEquals("00.0#E0: -12346", "-12.35E3", df.format(-12346)); + assertEquals("00.0#E0: -99999", "-10.0E4", df.format(-99999)); + + df = new DecimalFormat("##0.0E0", dfs); + assertEquals("##0.0E0: 0", "0.0E0", df.format(0)); + assertEquals("##0.0E0: 1", "1.0E0", df.format(1)); + assertEquals("##0.0E0: 12", "12E0", df.format(12)); + assertEquals("##0.0E0: 123", "123E0", df.format(123)); // Android fails, here! + assertEquals("##0.0E0: 1234", "1.234E3", df.format(1234)); + assertEquals("##0.0E0: 12346", "12.35E3", df.format(12346)); + // Fails in JDK 1.2.2 + if (!df.format(99999).equals("100E3")) + failures.set(failCount); + failCount++; + assertEquals("##0.0E0: 999999", "1.0E6", df.format(999999)); + + df = new DecimalFormat("#00.0##E0", dfs); + // Fails in JDK 1.2.2 + if (!df.format(0).equals("0.00E0")) + failures.set(failCount); + failCount++; + assertEquals("#00.0##E0: 1", "1.00E0", df.format(1)); + assertEquals("#00.0##E0: 12", "12.0E0", df.format(12)); + assertEquals("#00.0##E0: 123", "123E0", df.format(123)); + assertEquals("#00.0##E0: 1234", "1.234E3", df.format(1234)); + assertEquals("#00.0##E0: 12345", "12.345E3", df.format(12345)); + assertEquals("#00.0##E0: 123456", "123.456E3", df.format(123456)); + assertEquals("#00.0##E0: 1234567", "1.23457E6", df.format(1234567)); + assertEquals("#00.0##E0: 12345678", "12.3457E6", df.format(12345678)); + assertEquals("#00.0##E0: 99999999", "100E6", df.format(99999999)); + + df = new DecimalFormat("#.0E0", dfs); + assertEquals("#.0E0: 0", ".0E0", df.format(0)); + assertEquals("#.0E0: 1", ".1E1", df.format(1)); + assertEquals("#.0E0: 12", ".12E2", df.format(12)); + assertEquals("#.0E0: 123", ".12E3", df.format(123)); + assertEquals("#.0E0: 1234", ".12E4", df.format(1234)); + assertEquals("#.0E0: 9999", ".1E5", df.format(9999)); + + df = new DecimalFormat("0.#E0", dfs); + assertEquals("0.#E0: 0", "0E0", df.format(0)); + assertEquals("0.#E0: 1", "1E0", df.format(1)); + assertEquals("0.#E0: 12", "1.2E1", df.format(12)); + assertEquals("0.#E0: 123", "1.2E2", df.format(123)); + assertEquals("0.#E0: 1234", "1.2E3", df.format(1234)); + assertEquals("0.#E0: 9999", "1E4", df.format(9999)); + + assertTrue("Failed " + failures + " of " + failCount, + failures.length() == 0); + } + + /** + * @tests java.text.DecimalFormat#formatToCharacterIterator(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "formatToCharacterIterator", + args = {java.lang.Object.class} + ) + public void test_formatToCharacterIteratorLjava_lang_Object() { + + try { + // Regression for HARMONY-466 + new DecimalFormat().formatToCharacterIterator(null); + fail("NullPointerException expected"); + } catch (NullPointerException e) { + // expected + } + + new Support_DecimalFormat( + "test_formatToCharacterIteratorLjava_lang_Object") + .t_formatToCharacterIterator(); + } + + /** + * @tests java.text.DecimalFormat#format(double) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "format", + args = {double.class} + ) + @KnownFailure("This test should take into account (inexact) double " + + "representation. Fails on Both RI and Android at different places." + + "There is ticket for this failure because of parseDouble method " + + "returns different values on Android and RI.") + public void test_formatD() { + DecimalFormat format = (DecimalFormat) NumberFormat + .getInstance(Locale.ENGLISH); + format.setGroupingUsed(false); + format.setMaximumFractionDigits(400); + + for (int i = 0; i < 309; i++) { + String tval = "1"; + for (int j = 0; j < i; j++) + tval += "0"; + double d = Double.parseDouble(tval); + String result = format.format(d); + assertEquals(i + ") e:" + tval + " r:" + result, tval, result); + } + + for (int i = 0; i < 322; i++) { + String tval = "0."; + for (int j = 0; j < i; j++) + tval += "0"; + tval += "1"; + double d = Double.parseDouble(tval); + String result = format.format(d); + assertEquals(i + ") e:" + tval + " r:" + result, tval, result); + } + assertEquals("123456789012345", format.format(123456789012345.)); + assertEquals("1", "12345678901234.5", format.format(12345678901234.5)); + assertEquals("2", "1234567890123.25", format.format(1234567890123.25)); + assertEquals("3", "999999999999.375", format.format(999999999999.375)); + assertEquals("4", "99999999999.0625", format.format(99999999999.0625)); + assertEquals("5", "9999999999.03125", format.format(9999999999.03125)); + assertEquals("6", "999999999.015625", format.format(999999999.015625)); + assertEquals("7", "99999999.0078125", format.format(99999999.0078125)); + assertEquals("8", "9999999.00390625", format.format(9999999.00390625)); + assertEquals("9", "999999.001953125", format.format(999999.001953125)); + assertEquals("10", "9999.00048828125", format.format(9999.00048828125)); + assertEquals("11", "999.000244140625", format.format(999.000244140625)); + assertEquals("12", "99.0001220703125", format.format(99.0001220703125)); + assertEquals("13", "9.00006103515625", format.format(9.00006103515625)); + assertEquals("14", "0.000030517578125", format.format(0.000030517578125)); + } + + /** + * @tests java.text.DecimalFormat#getDecimalFormatSymbols() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getDecimalFormatSymbols", + args = {} + ) + public void test_getDecimalFormatSymbols() { + DecimalFormat df = (DecimalFormat) NumberFormat + .getInstance(Locale.ENGLISH); + DecimalFormatSymbols dfs = df.getDecimalFormatSymbols(); + assertTrue("Identical symbols", dfs != df.getDecimalFormatSymbols()); + } + + /** + * @tests java.text.DecimalFormat#getCurrency() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCurrency", + args = {} + ) + public void test_getCurrency() { + Currency currK = Currency.getInstance("KRW"); + Currency currX = Currency.getInstance("XXX"); + Currency currE = Currency.getInstance("EUR"); + Currency curr01; + + DecimalFormat df = (DecimalFormat) NumberFormat + .getCurrencyInstance(new Locale("ko", "KR")); + assertTrue("Test1: Returned incorrect currency", + df.getCurrency() == currK); + + df = (DecimalFormat) NumberFormat.getCurrencyInstance(new Locale("", + "KR")); + assertTrue("Test2: Returned incorrect currency", + df.getCurrency() == currK); + + df = (DecimalFormat) NumberFormat.getCurrencyInstance(new Locale("ko", + "")); + assertTrue("Test3: Returned incorrect currency", + df.getCurrency() == currX); + + df = (DecimalFormat) NumberFormat.getCurrencyInstance(new Locale("fr", + "FR")); + assertTrue("Test4: Returned incorrect currency", + df.getCurrency() == currE); + + // Regression for HARMONY-1351 + df = (DecimalFormat) NumberFormat.getCurrencyInstance(new Locale( + "QWERTY")); + assertTrue("Test5: Returned incorrect currency", + df.getCurrency() == currX); + + // JDK fails these tests since it doesn't have the PREEURO variant + // df = (DecimalFormat)NumberFormat.getCurrencyInstance(new Locale("fr", + // "FR","PREEURO")); + // assertTrue("Test5: Returned incorrect currency", df.getCurrency() == + // currF); + } + + /** + * @tests java.text.DecimalFormat#getGroupingSize() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getGroupingSize", + args = {} + ) + public void test_getGroupingSize() { + DecimalFormat df = new DecimalFormat("###0.##"); + assertEquals("Wrong unset size", 0, df.getGroupingSize()); + df = new DecimalFormat("#,##0.##"); + assertEquals("Wrong set size", 3, df.getGroupingSize()); + df = new DecimalFormat("#,###,###0.##"); + assertEquals("Wrong multiple set size", 4, df.getGroupingSize()); + } + + /** + * @tests java.text.DecimalFormat#getMultiplier() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMultiplier", + args = {} + ) + public void test_getMultiplier() { + final int defaultMultiplier = 1; + NumberFormat nform = DecimalFormat.getInstance(Locale.US); + DecimalFormat form = (DecimalFormat) nform; + assertEquals(defaultMultiplier, form.getMultiplier()); + + DecimalFormat df = new DecimalFormat("###0.##"); + assertEquals("Wrong unset multiplier", 1, df.getMultiplier()); + df = new DecimalFormat("###0.##%"); + assertEquals("Wrong percent multiplier", 100, df.getMultiplier()); + df = new DecimalFormat("###0.##\u2030"); + assertEquals("Wrong mille multiplier", 1000, df.getMultiplier()); + } + + /** + * @tests java.text.DecimalFormat#getNegativePrefix() Test of method + * java.text.DecimalFormat#getNegativePrefix(). + */ + @TestTargets({ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getNegativePrefix", + args = {} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setNegativePrefix", + args = {java.lang.String.class} + ) + }) + public void test_getNegativePrefix() { + DecimalFormat df = new DecimalFormat(); + try { + df.setNegativePrefix("--"); + assertTrue("Incorrect negative prefix", df.getNegativePrefix() + .equals("--")); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormat#getNegativeSuffix() Test of method + * java.text.DecimalFormat#getNegativeSuffix(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getNegativeSuffix", + args = {} + ) + public void test_getNegativeSuffix() { + DecimalFormat df = new DecimalFormat(); + try { + df.setNegativeSuffix("&"); + assertTrue("Incorrect negative suffix", df.getNegativeSuffix() + .equals("&")); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormat#getPositivePrefix() Test of method + * java.text.DecimalFormat#getPositivePrefix(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getPositivePrefix", + args = {} + ) + public void test_getPositivePrefix() { + DecimalFormat df = new DecimalFormat(); + try { + df.setPositivePrefix("++"); + assertTrue("Incorrect positive prefix", df.getPositivePrefix() + .equals("++")); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormat#getPositiveSuffix() Test of method + * java.text.DecimalFormat#getPositiveSuffix(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getPositiveSuffix", + args = {} + ) + public void test_getPositiveSuffix() { + DecimalFormat df = new DecimalFormat(); + try { + df.setPositiveSuffix("%"); + assertTrue("Incorrect positive prefix", df.getPositiveSuffix() + .equals("%")); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormat#hashCode() Test of method + * java.text.DecimalFormat#hashCode(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + try { + DecimalFormat df1 = new DecimalFormat(); + DecimalFormat df2 = (DecimalFormat) df1.clone(); + assertTrue("Hash codes of equals object are not equal", df2 + .hashCode() == df1.hashCode()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.DecimalFormat#isDecimalSeparatorAlwaysShown() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "isDecimalSeparatorAlwaysShown", + args = {} + ) + public void test_isDecimalSeparatorAlwaysShown() { + DecimalFormat df = new DecimalFormat("###0.##"); + assertTrue("Wrong unset value", !df.isDecimalSeparatorAlwaysShown()); + df = new DecimalFormat("###0.00"); + assertTrue("Wrong unset2 value", !df.isDecimalSeparatorAlwaysShown()); + df = new DecimalFormat("###0."); + assertTrue("Wrong set value", df.isDecimalSeparatorAlwaysShown()); + } + + /** + * @tests java.text.DecimalFormat#parse(java.lang.String, + * java.text.ParsePosition) + */ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Vrifies boundary values.", + method = "parse", + args = {java.lang.String.class, java.text.ParsePosition.class} + ) + @KnownFailure("Something seems wrong with Android implementation, here!") + public void test_parseLjava_lang_StringLjava_text_ParsePosition() { + DecimalFormat format = (DecimalFormat) NumberFormat + .getNumberInstance(Locale.ENGLISH); + ParsePosition pos = new ParsePosition(0); + Number result = format.parse("9223372036854775807", pos); + assertTrue("Wrong result type for Long.MAX_VALUE", + result.getClass() == Long.class); + assertEquals("Wrong result Long.MAX_VALUE", + Long.MAX_VALUE, result.longValue()); + pos = new ParsePosition(0); + result = format.parse("-9223372036854775808", pos); + assertTrue("Wrong result type for Long.MIN_VALUE", + result.getClass() == Long.class); + assertTrue("Wrong result Long.MIN_VALUE: " + result.longValue(), result + .longValue() == Long.MIN_VALUE); + pos = new ParsePosition(0); + result = format.parse("9223372036854775808", pos); + assertTrue("Wrong result type for Long.MAX_VALUE+1", + result.getClass() == Double.class); + assertEquals("Wrong result Long.MAX_VALUE + 1", + (double) Long.MAX_VALUE + 1, result.doubleValue()); + pos = new ParsePosition(0); + result = format.parse("-9223372036854775809", pos); + assertTrue("Wrong result type for Long.MIN_VALUE - 1", + result.getClass() == Double.class); + assertEquals("Wrong result Long.MIN_VALUE - 1", + (double) Long.MIN_VALUE - 1, result.doubleValue()); + + pos = new ParsePosition(0); + result = format.parse("18446744073709551629", pos); + assertTrue("Wrong result type for overflow", + result.getClass() == Double.class); + assertEquals("Wrong result for overflow", + 18446744073709551629d, result.doubleValue()); + + pos = new ParsePosition(0); + result = format.parse("42325917317067571199", pos); + assertTrue("Wrong result type for overflow a: " + result, result + .getClass() == Double.class); + assertTrue("Wrong result for overflow a: " + result, result + .doubleValue() == 42325917317067571199d); + pos = new ParsePosition(0); + result = format.parse("4232591731706757119E1", pos); + assertTrue("Wrong result type for overflow b: " + result, result + .getClass() == Double.class); + assertEquals("Wrong result for overflow b: " + result, + 42325917317067571190d, result.doubleValue()); + pos = new ParsePosition(0); + result = format.parse(".42325917317067571199E20", pos); + assertTrue("Wrong result type for overflow c: " + result, result + .getClass() == Double.class); + assertTrue("Wrong result for overflow c: " + result, result + .doubleValue() == 42325917317067571199d); + pos = new ParsePosition(0); + result = format.parse("922337203685477580.9E1", pos); + assertTrue("Wrong result type for overflow d: " + result, result + .getClass() == Double.class); + assertTrue("Wrong result for overflow d: " + result, result + .doubleValue() == 9223372036854775809d); + pos = new ParsePosition(0); + result = format.parse("9.223372036854775809E18", pos); + assertTrue("Wrong result type for overflow e: " + result, result + .getClass() == Double.class); + assertTrue("Wrong result for overflow e: " + result, result + .doubleValue() == 9223372036854775809d); + + // test parse with multipliers + format.setMultiplier(100); + result = format.parse("9223372036854775807", new ParsePosition(0)); + assertTrue("Wrong result type multiplier 100: " + result, result + .getClass() == Long.class); + // RI on windows and linux both answer with a slightly rounded result + assertTrue("Wrong result for multiplier 100: " + result, result + .longValue() == 92233720368547760L); + format.setMultiplier(1000); + result = format.parse("9223372036854775807", new ParsePosition(0)); + assertTrue("Wrong result type multiplier 1000: " + result, result + .getClass() == Long.class); + assertTrue("Wrong result for multiplier 1000: " + result, result + .longValue() == 9223372036854776L); + + format.setMultiplier(10000); + result = format.parse("9223372036854775807", new ParsePosition(0)); + assertTrue("Wrong result type multiplier 10000: " + result, result + .getClass() == Double.class); + assertTrue("Wrong result for multiplier 10000: " + result, result + .doubleValue() == 922337203685477.5807d); + + } + + /** + * @tests java.text.DecimalFormat#setDecimalFormatSymbols(java.text.DecimalFormatSymbols) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setDecimalFormatSymbols", + args = {java.text.DecimalFormatSymbols.class} + ) + public void test_setDecimalFormatSymbolsLjava_text_DecimalFormatSymbols() { + DecimalFormat df = new DecimalFormat("###0.##"); + DecimalFormatSymbols dfs = new DecimalFormatSymbols(); + dfs.setDecimalSeparator('@'); + df.setDecimalFormatSymbols(dfs); + assertTrue("Not set", df.getDecimalFormatSymbols().equals(dfs)); + assertEquals("Symbols not used", "1@2", df.format(1.2)); + + // The returned symbols may be cloned in two spots + // 1. When set + // 2. When returned + DecimalFormat format = new DecimalFormat(); + DecimalFormatSymbols symbols = new DecimalFormatSymbols(); + format.setDecimalFormatSymbols(symbols); + DecimalFormatSymbols symbolsOut = format.getDecimalFormatSymbols(); + assertNotSame(symbols, symbolsOut); + } + + /** + * @tests java.text.DecimalFormat#setDecimalSeparatorAlwaysShown(boolean) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setDecimalSeparatorAlwaysShown", + args = {boolean.class} + ) + public void test_setDecimalSeparatorAlwaysShownZ() { + DecimalFormat df = new DecimalFormat("###0.##", + new DecimalFormatSymbols(Locale.US)); + assertEquals("Wrong default result", "5", df.format(5)); + df.setDecimalSeparatorAlwaysShown(true); + assertTrue("Not set", df.isDecimalSeparatorAlwaysShown()); + assertEquals("Wrong set result", "7.", df.format(7)); + + df.setDecimalSeparatorAlwaysShown(false); + assertFalse("Returns true", df.isDecimalSeparatorAlwaysShown()); + assertEquals("Wrong set result", "8", df.format(8)); + } + + /** + * @tests java.text.DecimalFormat#setCurrency(java.util.Currency) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setCurrency", + args = {java.util.Currency.class} + ) + public void test_setCurrencyLjava_util_Currency() { + Locale locale = Locale.CANADA; + DecimalFormat df = ((DecimalFormat) NumberFormat + .getCurrencyInstance(locale)); + + try { + df.setCurrency(null); + fail("Expected NullPointerException"); + } catch (NullPointerException e) { + } + + Currency currency = Currency.getInstance("AED"); + df.setCurrency(currency); + assertTrue("Returned incorrect currency", currency == df.getCurrency()); + assertTrue("Returned incorrect currency symbol", currency.getSymbol( + locale) + .equals(df.getDecimalFormatSymbols().getCurrencySymbol())); + assertTrue("Returned incorrect international currency symbol", currency + .getCurrencyCode().equals( + df.getDecimalFormatSymbols() + .getInternationalCurrencySymbol())); + } + + /** + * @tests java.text.DecimalFormat#setGroupingSize(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setGroupingSize", + args = {int.class} + ) + public void test_setGroupingSizeI() { + DecimalFormat df = new DecimalFormat("###0.##", + new DecimalFormatSymbols(Locale.ENGLISH)); + df.setGroupingUsed(true); + df.setGroupingSize(2); + assertEquals("Value not set", 2, df.getGroupingSize()); + String result = df.format(123); + assertTrue("Invalid format:" + result, result.equals("1,23")); + } + + /** + * @tests java.text.DecimalFormat#setMaximumFractionDigits(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMaximumFractionDigits", + args = {int.class} + ) + public void test_setMaximumFractionDigitsI() { + DecimalFormat df = new DecimalFormat("###0.##", + new DecimalFormatSymbols(Locale.US)); + df.setMaximumFractionDigits(3); + assertEquals("Not set", 3, df.getMaximumFractionDigits()); + assertEquals("Wrong maximum", "1.235", df.format(1.23456)); + df.setMinimumFractionDigits(4); + assertEquals("Not changed", 4, df.getMaximumFractionDigits()); + assertEquals("Incorrect fraction", "456.0000", df.format(456)); + } + + /** + * @tests java.text.DecimalFormat#setMaximumIntegerDigits(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMaximumIntegerDigits", + args = {int.class} + ) + public void test_setMaximumIntegerDigitsI() { + DecimalFormat df = new DecimalFormat("###0.##"); + df.setMaximumIntegerDigits(2); + assertEquals("Not set", 2, df.getMaximumIntegerDigits()); + assertEquals("Wrong maximum", "34", df.format(1234)); + df.setMinimumIntegerDigits(4); + assertEquals("Not changed", 4, df.getMaximumIntegerDigits()); + assertEquals("Incorrect integer", "0026", df.format(26)); + } + + /** + * @tests java.text.DecimalFormat#setMinimumFractionDigits(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMinimumFractionDigits", + args = {int.class} + ) + public void test_setMinimumFractionDigitsI() { + DecimalFormat df = new DecimalFormat("###0.##", + new DecimalFormatSymbols(Locale.US)); + df.setMinimumFractionDigits(4); + assertEquals("Not set", 4, df.getMinimumFractionDigits()); + assertEquals("Wrong minimum", "1.2300", df.format(1.23)); + df.setMaximumFractionDigits(2); + assertEquals("Not changed", 2, df.getMinimumFractionDigits()); + assertEquals("Incorrect fraction", "456.00", df.format(456)); + } + + /** + * @tests java.text.DecimalFormat#setMinimumIntegerDigits(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMinimumIntegerDigits", + args = {int.class} + ) + public void test_setMinimumIntegerDigitsI() { + DecimalFormat df = new DecimalFormat("###0.##", + new DecimalFormatSymbols(Locale.US)); + df.setMinimumIntegerDigits(3); + assertEquals("Not set", 3, df.getMinimumIntegerDigits()); + assertEquals("Wrong minimum", "012", df.format(12)); + df.setMaximumIntegerDigits(2); + assertEquals("Not changed", 2, df.getMinimumIntegerDigits()); + assertEquals("Incorrect integer", "00.7", df.format(0.7)); + } + + /** + * @tests java.text.DecimalFormat#setMultiplier(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMultiplier", + args = {int.class} + ) + public void test_setMultiplierI() { + DecimalFormat df = new DecimalFormat("###0.##"); + df.setMultiplier(10); + assertEquals("Wrong multiplier", 10, df.getMultiplier()); + assertEquals("Wrong format", "50", df.format(5)); + assertEquals("Wrong parse", 5, df.parse("50", new ParsePosition(0)) + .intValue()); + + // regression test for HARMONY-879 + df.setMultiplier(-1); + assertEquals("Wrong multiplier for negative value", -1, df + .getMultiplier()); + } + + /** + * @tests serialization/deserialization compatibility. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Verifies serialization/deserialization compatibility.", + method = "!SerializationSelf", + args = {} + ) + + public void testSerializationSelf() throws Exception { + // SerializationTest.verifySelf(new DecimalFormat()); + } + + /** + * @tests serialization compatibility with RI + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Verifies serialization compatibility.", + method = "!SerializationGolden", + args = {} + ) + public void test_serializationHarmonyRICompatible() { + NumberFormat nf = NumberFormat.getInstance(Locale.FRANCE); + + DecimalFormat df = null; + if (!(nf instanceof DecimalFormat)) { + throw new Error("This NumberFormat is not a DecimalFormat"); + + } + df = (DecimalFormat) nf; + + ObjectInputStream oinput = null; + + DecimalFormat deserializedDF = null; + + try { + oinput = new ObjectInputStream(this.getClass().getResource( + "/serialization/java/text/DecimalFormat.ser").openStream()); + deserializedDF = (DecimalFormat) oinput.readObject(); + } catch (Exception e) { + fail("Error occurs during deserialization"); + } finally { + try { + if (null != oinput) { + oinput.close(); + } + } catch (Exception e) { + // ignore + } + } + + assertEquals(df.getNegativePrefix(), deserializedDF.getNegativePrefix()); + assertEquals(df.getNegativeSuffix(), deserializedDF.getNegativeSuffix()); + assertEquals(df.getPositivePrefix(), deserializedDF.getPositivePrefix()); + assertEquals(df.getPositiveSuffix(), deserializedDF.getPositiveSuffix()); + assertEquals(df.getCurrency(), deserializedDF.getCurrency()); + + assertEquals(df.getDecimalFormatSymbols(), deserializedDF + .getDecimalFormatSymbols()); + + assertEquals(df.getGroupingSize(), df.getGroupingSize()); + assertEquals(df.getMaximumFractionDigits(), deserializedDF + .getMaximumFractionDigits()); + + assertEquals(df.getMaximumIntegerDigits(), deserializedDF + .getMaximumIntegerDigits()); + + assertEquals(df.getMinimumFractionDigits(), deserializedDF + .getMinimumFractionDigits()); + assertEquals(df.getMinimumIntegerDigits(), deserializedDF + .getMinimumIntegerDigits()); + assertEquals(df.getMultiplier(), deserializedDF.getMultiplier()); + + // Deliberately omitted this assertion. Since different data resource + // will cause the assertion fail. + // assertEquals(df, deserializedDF); + + } + + /** + * Test whether DecimalFormat can parse Positive infinity correctly + */ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test.", + method = "parse", + args = {java.lang.String.class, java.text.ParsePosition.class} + ) + public void testParseInfinityBigDecimalFalse() { + // Regression test for HARMONY-106 + DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance(); + DecimalFormatSymbols symbols = new DecimalFormatSymbols(); + Number number = format.parse(symbols.getInfinity(), + new ParsePosition(0)); + assertTrue(number instanceof Double); + assertTrue(Double.isInfinite(number.doubleValue())); + } + + /** + * Test whether DecimalFormat can parse Negative infinity correctly + */ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test.", + method = "parse", + args = {java.lang.String.class, java.text.ParsePosition.class} + ) + public void testParseMinusInfinityBigDecimalFalse() { + // Regression test for HARMONY-106 + DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance(); + DecimalFormatSymbols symbols = new DecimalFormatSymbols(); + Number number = format.parse("-" + symbols.getInfinity(), + new ParsePosition(0)); + assertTrue(number instanceof Double); + assertTrue(Double.isInfinite(number.doubleValue())); + } + + /** + * Test if setDecimalFormatSymbols method wont throw NullPointerException + * when it is called with null parameter. + */ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Verifies null as a parameter.", + method = "setDecimalFormatSymbols", + args = {java.text.DecimalFormatSymbols.class} + ) + public void testSetDecimalFormatSymbolsAsNull() { + // Regression for HARMONY-1070 + try { + DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance(); + format.setDecimalFormatSymbols(null); + } catch (Exception e) { + fail("Unexpected exception caught: " + e); + } + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/DecimalFormatTestICU.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/DecimalFormatTestICU.java new file mode 100644 index 0000000..08ae892 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/DecimalFormatTestICU.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.AndroidOnly; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * Test for additional features introduced by icu. These tests fail on the RI + * but succeed on Android 1.0. The features are only accessible through the + * pattern. Api methods where not introduced to allow direct access of these + * features. This would have changed the api too much. + */ +@TestTargetClass(DecimalFormat.class) +public class DecimalFormatTestICU extends TestCase { + + DecimalFormat format; + + protected void setUp() { + format = (DecimalFormat) NumberFormat.getNumberInstance(); + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Regression test.", + method = "format", + args = {java.lang.Object.class} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Regression test.", + method = "parse", + args = {java.lang.String.class} + ) + }) + @AndroidOnly("special feature of icu4c") + public void test_sigDigitPatterns() throws Exception { + DecimalFormat format = (DecimalFormat) NumberFormat + .getInstance(Locale.US); + + format.applyPattern("@@@"); + assertEquals("sigDigit doesn't work", "12300", format.format(12345)); + assertEquals("sigDigit doesn't work", "0.123", format.format(0.12345)); + + format.applyPattern("@@##"); + assertEquals("sigDigit doesn't work", "3.142", format.format(3.14159)); + assertEquals("sigDigit doesn't work", "1.23", format.format(1.23004)); + + format.applyPattern("@@###E0"); + assertEquals("1.23E1", format.format(12.3)); + format.applyPattern("0.0###E0"); + assertEquals("1.23E1", format.format(12.3)); + + try { + format.applyPattern("@00"); + fail("expected IllegalArgumentException was not thrown for " + + "pattern \"@00\"."); + } catch (IllegalArgumentException e) { + // expected + } + + try { + format.applyPattern("@.###"); + fail("expected IllegalArgumentException was not thrown for " + + "pattern \"@.###\"."); + } catch (IllegalArgumentException e) { + // expected + } + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test.", + method = "format", + args = {java.lang.Object.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test.", + method = "parse", + args = {java.lang.String.class} + ) + }) + @AndroidOnly("special feature of icu4c") + public void test_paddingPattern() throws Exception { + format.applyPattern("*x##,##,#,##0.0#"); + assertEquals("xxxxxxxxx123.0", format.format(123)); + assertEquals(123, format.parse("xxxxxxxxx123.0").intValue()); + + format.applyPattern("$*x#,##0.00"); + assertEquals("$xx123.00", format.format(123)); + assertEquals("$1,234.00", format.format(1234)); + + format.applyPattern("*\u00e7#0 o''clock"); + assertEquals("\u00e72 o'clock", format.format(2)); + assertEquals("12 o'clock", format.format(12)); + assertEquals(2, format.parse("\u00e72 o'clock").intValue()); + assertEquals(12, format.parse("12 o'clock").intValue()); + + try { + format.applyPattern("#0.##*xE0"); + fail("expected IllegalArgumentException was not thrown for" + + "pattern \"#0.##*xE0\"."); + } catch (IllegalArgumentException e) { + // expected + } + + try { + format.applyPattern("##0.## *"); + fail("expected IllegalArgumentException was not thrown for " + + "pattern \"##0.## *\"."); + } catch (IllegalArgumentException e) { + // expected + } + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test.", + method = "format", + args = {java.lang.Object.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test.", + method = "parse", + args = {java.lang.String.class} + ) + }) + @AndroidOnly("special feature of icu4c") + public void test_positiveExponentSign() throws Exception { + format.applyPattern("0.###E+0"); + assertEquals("1E+2", format.format(100)); + assertEquals("1E-2", format.format(0.01)); + assertEquals(100, format.parse("1E+2").intValue()); + assertEquals(0.01f, format.parse("1E-2").floatValue()); + + format.applyPattern("0.###E0 m/s"); + assertEquals("1E2 m/s", format.format(100)); + assertEquals(100, format.parse("1E2 m/s").intValue()); + + format.applyPattern("00.###E0"); + assertEquals("12.3E-4", format.format(0.00123)); + assertEquals(0.00123f, format.parse("12.3E-4").floatValue()); + + format.applyPattern("##0.####E0"); + assertEquals("12.345E3", format.format(12345)); + assertEquals(12345, format.parse("12.345E3").intValue()); + + try { + format.applyPattern("#,##0.##E0"); + fail("expected IllegalArgumentException was not thrown for " + + "pattern \"#,##0.##E0\"."); + } catch (IllegalArgumentException e) { + // expected + } + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Verifies the grouping size.", + method = "format", + args = {java.lang.Object.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Verifies the grouping size.", + method = "parse", + args = {java.lang.String.class} + ) + }) + @AndroidOnly("special feature of icu4c") + public void test_secondaryGroupingSize() throws Exception { + format.applyPattern("#,##,###,####"); + assertEquals("123,456,7890", format.format(1234567890)); + assertEquals(1234567890, format.parse("123,456,7890").intValue()); + format.applyPattern("##,#,###,####"); + assertEquals("123,456,7890", format.format(1234567890)); + assertEquals(1234567890, format.parse("123,456,7890").intValue()); + format.applyPattern("###,###,####"); + assertEquals("123,456,7890", format.format(1234567890)); + assertEquals(1234567890, format.parse("123,456,7890").intValue()); + + format.applyPattern("###,##,###.#"); + assertEquals("12,34,567.8", format.format(1234567.8)); + format.applyPattern("##,#,##,###.#"); + assertEquals("12,34,567.8", format.format(1234567.8)); + format.applyPattern("#,##,##,###.#"); + assertEquals("12,34,567.8", format.format(1234567.8)); + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/FieldPositionTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/FieldPositionTest.java new file mode 100644 index 0000000..7df1a33 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/FieldPositionTest.java @@ -0,0 +1,338 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import java.text.DateFormat; +import java.text.FieldPosition; + +@TestTargetClass(FieldPosition.class) +public class FieldPositionTest extends junit.framework.TestCase { + + /** + * @tests java.text.FieldPosition#FieldPosition(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "FieldPosition", + args = {int.class} + ) + public void test_ConstructorI() { + // Test for constructor java.text.FieldPosition(int) + FieldPosition fpos = new FieldPosition(DateFormat.MONTH_FIELD); + assertEquals("Test1: Constructor failed to set field identifier!", + DateFormat.MONTH_FIELD, fpos.getField()); + assertNull("Constructor failed to set field attribute!", fpos + .getFieldAttribute()); + } + + /** + * @tests java.text.FieldPosition#FieldPosition(java.text.Format$Field) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "FieldPosition", + args = {java.text.Format.Field.class} + ) + public void test_ConstructorLjava_text_Format$Field() { + // Test for constructor java.text.FieldPosition(Format.Field) + FieldPosition fpos = new FieldPosition(DateFormat.Field.MONTH); + assertSame("Constructor failed to set field attribute!", + DateFormat.Field.MONTH, fpos.getFieldAttribute()); + assertEquals("Test1: Constructor failed to set field identifier!", -1, + fpos.getField()); + } + + /** + * @tests java.text.FieldPosition#FieldPosition(java.text.Format$Field, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "FieldPosition", + args = {java.text.Format.Field.class, int.class} + ) + public void test_ConstructorLjava_text_Format$FieldI() { + // Test for constructor java.text.FieldPosition(Format.Field, int) + FieldPosition fpos = new FieldPosition(DateFormat.Field.MONTH, + DateFormat.MONTH_FIELD); + assertSame("Constructor failed to set field attribute!", + DateFormat.Field.MONTH, fpos.getFieldAttribute()); + assertEquals("Test1: Constructor failed to set field identifier!", + DateFormat.MONTH_FIELD, fpos.getField()); + + // test special cases + FieldPosition fpos2 = new FieldPosition(DateFormat.Field.HOUR1, + DateFormat.HOUR1_FIELD); + assertSame("Constructor failed to set field attribute!", + DateFormat.Field.HOUR1, fpos2.getFieldAttribute()); + assertEquals("Test2: Constructor failed to set field identifier!", + DateFormat.HOUR1_FIELD, fpos2.getField()); + + FieldPosition fpos3 = new FieldPosition(DateFormat.Field.TIME_ZONE, + DateFormat.MONTH_FIELD); + assertSame("Constructor failed to set field attribute!", + DateFormat.Field.TIME_ZONE, fpos3.getFieldAttribute()); + assertEquals("Test3: Constructor failed to set field identifier!", + DateFormat.MONTH_FIELD, fpos3.getField()); + } + + /** + * @tests java.text.FieldPosition#equals(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + // Test for method boolean + // java.text.FieldPosition.equals(java.lang.Object) + FieldPosition fpos = new FieldPosition(1); + FieldPosition fpos1 = new FieldPosition(1); + assertTrue("Identical objects were not equal!", fpos.equals(fpos1)); + + FieldPosition fpos2 = new FieldPosition(2); + assertTrue("Objects with a different ID should not be equal!", !fpos + .equals(fpos2)); + + fpos.setBeginIndex(1); + fpos1.setBeginIndex(2); + assertTrue("Objects with a different beginIndex were still equal!", + !fpos.equals(fpos1)); + fpos1.setBeginIndex(1); + fpos1.setEndIndex(2); + assertTrue("Objects with a different endIndex were still equal!", !fpos + .equals(fpos1)); + + FieldPosition fpos3 = new FieldPosition(DateFormat.Field.ERA, 1); + assertTrue("Objects with a different attribute should not be equal!", + !fpos.equals(fpos3)); + FieldPosition fpos4 = new FieldPosition(DateFormat.Field.AM_PM, 1); + assertTrue("Objects with a different attribute should not be equal!", + !fpos3.equals(fpos4)); + } + + /** + * @tests java.text.FieldPosition#getBeginIndex() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getBeginIndex", + args = {} + ) + public void test_getBeginIndex() { + // Test for method int java.text.FieldPosition.getBeginIndex() + FieldPosition fpos = new FieldPosition(1); + fpos.setEndIndex(3); + fpos.setBeginIndex(2); + assertEquals("getBeginIndex should have returned 2", 2, fpos + .getBeginIndex()); + } + + /** + * @tests java.text.FieldPosition#getEndIndex() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getEndIndex", + args = {} + ) + public void test_getEndIndex() { + // Test for method int java.text.FieldPosition.getEndIndex() + FieldPosition fpos = new FieldPosition(1); + fpos.setBeginIndex(2); + fpos.setEndIndex(3); + assertEquals("getEndIndex should have returned 3", 3, fpos + .getEndIndex()); + } + + /** + * @tests java.text.FieldPosition#getField() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getField", + args = {} + ) + public void test_getField() { + // Test for method int java.text.FieldPosition.getField() + FieldPosition fpos = new FieldPosition(65); + assertEquals( + "FieldPosition(65) should have caused getField to return 65", + 65, fpos.getField()); + FieldPosition fpos2 = new FieldPosition(DateFormat.Field.MINUTE); + assertEquals( + "FieldPosition(DateFormat.Field.MINUTE) should have caused getField to return -1", + -1, fpos2.getField()); + } + + /** + * @tests java.text.FieldPosition#getFieldAttribute() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getFieldAttribute", + args = {} + ) + public void test_getFieldAttribute() { + // Test for method int java.text.FieldPosition.getFieldAttribute() + FieldPosition fpos = new FieldPosition(DateFormat.Field.TIME_ZONE); + assertTrue( + "FieldPosition(DateFormat.Field.TIME_ZONE) should have caused getFieldAttribute to return DateFormat.Field.TIME_ZONE", + fpos.getFieldAttribute() == DateFormat.Field.TIME_ZONE); + + FieldPosition fpos2 = new FieldPosition(DateFormat.TIMEZONE_FIELD); + assertNull( + "FieldPosition(DateFormat.TIMEZONE_FIELD) should have caused getFieldAttribute to return null", + fpos2.getFieldAttribute()); + } + + /** + * @tests java.text.FieldPosition#hashCode() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + // Test for method int java.text.FieldPosition.hashCode() + FieldPosition fpos1 = new FieldPosition(1); + FieldPosition fpos2 = new FieldPosition(1); + assertTrue("test 1: hash codes are not equal for equal objects.", + fpos1.hashCode() == fpos2.hashCode()); + fpos1.setBeginIndex(5); + fpos1.setEndIndex(110); + assertTrue("test 2: hash codes are equal for non equal objects.", + fpos1.hashCode() != fpos2.hashCode()); + fpos2.setBeginIndex(5); + fpos2.setEndIndex(110); + assertTrue("test 3: hash codes are not equal for equal objects.", + fpos1.hashCode() == fpos2.hashCode()); + + FieldPosition fpos3 = new FieldPosition( + DateFormat.Field.DAY_OF_WEEK_IN_MONTH); + + assertTrue("test 4: hash codes are equal for non equal objects.", + fpos2.hashCode() != fpos3.hashCode()); + } + + /** + * @tests java.text.FieldPosition#setBeginIndex(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setBeginIndex", + args = {int.class} + ) + public void test_setBeginIndexI() { + // Test for method void java.text.FieldPosition.setBeginIndex(int) + FieldPosition fpos = new FieldPosition(1); + fpos.setBeginIndex(2); + fpos.setEndIndex(3); + assertEquals("beginIndex should have been set to 2", 2, fpos + .getBeginIndex()); + + fpos.setBeginIndex(Integer.MAX_VALUE); + assertEquals("beginIndex should have been set to Integer.MAX_VALUE", + Integer.MAX_VALUE, fpos.getBeginIndex()); + + fpos.setBeginIndex(-1); + assertEquals("beginIndex should have been set to -1", + -1, fpos.getBeginIndex()); + } + + /** + * @tests java.text.FieldPosition#setEndIndex(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setEndIndex", + args = {int.class} + ) + public void test_setEndIndexI() { + // Test for method void java.text.FieldPosition.setEndIndex(int) + FieldPosition fpos = new FieldPosition(1); + fpos.setEndIndex(3); + fpos.setBeginIndex(2); + assertEquals("EndIndex should have been set to 3", 3, fpos + .getEndIndex()); + + fpos.setEndIndex(Integer.MAX_VALUE); + assertEquals("endIndex should have been set to Integer.MAX_VALUE", + Integer.MAX_VALUE, fpos.getEndIndex()); + + fpos.setEndIndex(-1); + assertEquals("endIndex should have been set to -1", + -1, fpos.getEndIndex()); + } + + /** + * @tests java.text.FieldPosition#toString() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "toString", + args = {} + ) + public void test_toString() { + // Test for method java.lang.String java.text.FieldPosition.toString() + FieldPosition fpos = new FieldPosition(1); + fpos.setBeginIndex(2); + fpos.setEndIndex(3); + // string format is not specified + assertNotNull( + "toString returned null", + fpos.toString()); + + FieldPosition fpos2 = new FieldPosition(DateFormat.Field.ERA); + fpos2.setBeginIndex(4); + fpos2.setEndIndex(5); + assertNotNull("ToString returned the wrong value:", + fpos2.toString()); + } + + /** + * Sets up the fixture, for example, open a network connection. This method + * is called before a test is executed. + */ + protected void setUp() { + } + + /** + * Tears down the fixture, for example, close a network connection. This + * method is called after a test is executed. + */ + protected void tearDown() { + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/FormatFieldTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/FormatFieldTest.java new file mode 100644 index 0000000..cf1c124 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/FormatFieldTest.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.text.Format; + + +@TestTargetClass(Format.Field.class) +public class FormatFieldTest extends TestCase { + private class MockFormatField extends Format.Field { + + private static final long serialVersionUID = 1L; + + public MockFormatField(String name) { + super(name); + } + } + + /** + * @tests java.text.Format.Field#FormatField(java.lang.String) Test of + * method java.text.Format.Field#FormatField(java.lang.String). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Field", + args = {java.lang.String.class} + ) + public void test_Constructor() { + try { + new MockFormatField("test"); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/FormatTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/FormatTest.java new file mode 100644 index 0000000..4c5cc36 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/FormatTest.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.text.AttributedCharacterIterator; +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParseException; +import java.text.ParsePosition; + + +@TestTargetClass(Format.class) +public class FormatTest extends TestCase { + private class MockFormat extends Format { + + public StringBuffer format(Object obj, StringBuffer toAppendTo, + FieldPosition pos) { + // it is a fake + if (obj == null) + throw new NullPointerException("obj is null"); + return new StringBuffer(""); + } + + public Object parseObject(String source, ParsePosition pos) { + // it is a fake + return null; + } + } + + /** + * @tests java.text.Format#format(Object) Test of method + * java.text.Format#format(Object). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Format", + args = {} + ) + public void test_Constructor() { + try { + new MockFormat(); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.Format#clone() Test of method java.text.Format#clone(). + * Compare of internal variables of cloned objects. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + try { + // Compare of internal variables of cloned objects + Format fm = new MockFormat(); + Format fmc = (Format) fm.clone(); + assertEquals(fm.getClass(), fmc.getClass()); + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.Format#format(java.lang.Object) Test of method + * java.text.Format#format(java.lang.Object). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Verifies that format(Object) calls format(Object, StringBuffer, FieldPosition) method.", + method = "format", + args = {java.lang.Object.class} + ) + public void test_formatLjava_lang_Object() { + + MockFormat mf = new MockFormat(); + assertEquals("", mf.format("")); + assertTrue("It calls an abstract metod format", true); + } + + /** + * @tests java.text.Format#formatToCharacterIterator(java.lang.Object) Test + * of method + * java.text.Format#formatToCharacterIterator(java.lang.Object). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "formatToCharacterIterator", + args = {java.lang.Object.class} + ) + public void test_formatToCharacterIteratorLjava_lang_Object() { + + MockFormat mf = new MockFormat(); + AttributedCharacterIterator aci = + mf.formatToCharacterIterator("Test 123 Test"); + + assertEquals(0, aci.getBeginIndex()); + + try { + mf.formatToCharacterIterator(null); + fail("NullPointerException was not thrown."); + } catch(NullPointerException npe) { + //expected + } + + try { + mf.formatToCharacterIterator(""); + } catch(IllegalArgumentException iae) { + //expected + } + } + + /** + * @tests java.text.Format#parseObject(java.lang.String source) Test of + * method java.text.Format#parseObject(java.lang.String source). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Verifies that parseObject(String) method calls parseObject(String source, ParsePosition pos) method.", + method = "parseObject", + args = {java.lang.String.class} + ) + public void test_parseObjectLjava_lang_String() { + MockFormat mf = new MockFormat(); + try { + assertNull(mf.parseObject("")); + fail("ParseException was not thrown."); + } catch (ParseException e) { + //expected + } + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/MessageFormatFieldTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/MessageFormatFieldTest.java new file mode 100644 index 0000000..b5302b6 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/MessageFormatFieldTest.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.MessageFormat; + +@TestTargetClass(MessageFormat.Field.class) +public class MessageFormatFieldTest extends TestCase { + /** + * @tests java.text.MessageFormat$Field#Field(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Field", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() { + // protected constructor + String name = "new Message format"; + MyMessageFormat field = new MyMessageFormat(name); + assertEquals("field has wrong name", name, field.getName()); + + field = new MyMessageFormat(null); + assertEquals("field has wrong name", null, field.getName()); + } + + /** + * @tests java.text.MessageFormat$Field#readResolve() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "readResolve", + args = {} + ) + public void test_readResolve() { + // test for method java.lang.Object readResolve() + + // see serialization stress tests: + // implemented in + // SerializationStressTest4.test_writeObject_MessageFormat_Field() + ObjectOutputStream out = null; + ObjectInputStream in = null; + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + out = new ObjectOutputStream(bytes); + + MessageFormat.Field mfield, mfield2; + MyMessageFormat field; + + mfield = MessageFormat.Field.ARGUMENT; + + field = new MyMessageFormat(null); + + out.writeObject(mfield); + out.writeObject(field); + + in = new ObjectInputStream(new ByteArrayInputStream(bytes + .toByteArray())); + + try { + mfield2 = (MessageFormat.Field) in.readObject(); + assertSame("resolved incorrectly", mfield, mfield2); + } catch (IllegalArgumentException e) { + fail("Unexpected IllegalArgumentException: " + e); + } + + try { + in.readObject(); + fail("Expected InvalidObjectException for subclass instance with null name"); + } catch (InvalidObjectException e) { + } + + } catch (IOException e) { + fail("unexpected IOException" + e); + } catch (ClassNotFoundException e) { + fail("unexpected ClassNotFoundException" + e); + } finally { + try { + if (out != null) + out.close(); + if (in != null) + in.close(); + } catch (IOException e) { + } + } + } + + static class MyMessageFormat extends MessageFormat.Field { + static final long serialVersionUID = 1L; + + protected MyMessageFormat(String attr) { + super(attr); + } + + protected String getName() { + return super.getName(); + } + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/MessageFormatTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/MessageFormatTest.java new file mode 100644 index 0000000..0265a51 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/MessageFormatTest.java @@ -0,0 +1,1307 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; + +import junit.framework.TestCase; + +import tests.support.Support_MessageFormat; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.ChoiceFormat; +import java.text.DateFormat; +import java.text.FieldPosition; +import java.text.Format; +import java.text.MessageFormat; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.TimeZone; + +@TestTargetClass(MessageFormat.class) +public class MessageFormatTest extends TestCase { + + private MessageFormat format1, format2, format3; + + private Locale defaultLocale; + + private void checkSerialization(MessageFormat format) { + try { + ByteArrayOutputStream ba = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(ba); + out.writeObject(format); + out.close(); + ObjectInputStream in = new ObjectInputStream( + new ByteArrayInputStream(ba.toByteArray())); + MessageFormat read = (MessageFormat) in.readObject(); + assertTrue("Not equal: " + format.toPattern(), format.equals(read)); + } catch (IOException e) { + fail("Format: " + format.toPattern() + " caused IOException: " + e); + } catch (ClassNotFoundException e) { + fail("Format: " + format.toPattern() + + " caused ClassNotFoundException: " + e); + } + } + + /** + * @tests java.text.MessageFormat#MessageFormat(java.lang.String, + * java.util.Locale) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IllegalArgumentException is not verified.", + method = "MessageFormat", + args = {java.lang.String.class, java.util.Locale.class} + ) + public void test_ConstructorLjava_lang_StringLjava_util_Locale() { + // Test for method java.text.MessageFormat(java.lang.String, + // java.util.Locale) + Locale mk = new Locale("mk", "MK"); + MessageFormat format = new MessageFormat( + "Date: {0,date} Currency: {1, number, currency} Integer: {2, number, integer}", + mk); + + assertTrue("Wrong locale1", format.getLocale().equals(mk)); + assertTrue("Wrong locale2", format.getFormats()[0].equals(DateFormat + .getDateInstance(DateFormat.DEFAULT, mk))); + assertTrue("Wrong locale3", format.getFormats()[1].equals(NumberFormat + .getCurrencyInstance(mk))); + assertTrue("Wrong locale4", format.getFormats()[2].equals(NumberFormat + .getIntegerInstance(mk))); + } + + /** + * @tests java.text.MessageFormat#MessageFormat(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "MessageFormat", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() { + // Test for method java.text.MessageFormat(java.lang.String) + MessageFormat format = new MessageFormat( + "abc {4,time} def {3,date} ghi {2,number} jkl {1,choice,0#low|1#high} mnop {0}"); + assertTrue("Not a MessageFormat", + format.getClass() == MessageFormat.class); + Format[] formats = format.getFormats(); + assertNotNull("null formats", formats); + assertTrue("Wrong format count: " + formats.length, formats.length >= 5); + assertTrue("Wrong time format", formats[0].equals(DateFormat + .getTimeInstance())); + assertTrue("Wrong date format", formats[1].equals(DateFormat + .getDateInstance())); + assertTrue("Wrong number format", formats[2].equals(NumberFormat + .getInstance())); + assertTrue("Wrong choice format", formats[3].equals(new ChoiceFormat( + "0.0#low|1.0#high"))); + assertNull("Wrong string format", formats[4]); + + Date date = new Date(); + FieldPosition pos = new FieldPosition(-1); + StringBuffer buffer = new StringBuffer(); + format.format(new Object[] { "123", new Double(1.6), new Double(7.2), + date, date }, buffer, pos); + String result = buffer.toString(); + buffer.setLength(0); + buffer.append("abc "); + buffer.append(DateFormat.getTimeInstance().format(date)); + buffer.append(" def "); + buffer.append(DateFormat.getDateInstance().format(date)); + buffer.append(" ghi "); + buffer.append(NumberFormat.getInstance().format(new Double(7.2))); + buffer.append(" jkl high mnop 123"); + assertTrue("Wrong answer:\n" + result + "\n" + buffer, result + .equals(buffer.toString())); + + assertEquals("Simple string", "Test message", new MessageFormat( + "Test message").format(new Object[0])); + + try { + result = new MessageFormat("Don't").format(new Object[0]); + assertTrue("Should not throw IllegalArgumentException: " + result, + "Dont".equals(result)); + } catch (Exception e) { + fail("Unexpected exception: " + e); + } + + try { + new MessageFormat("Invalid {1,foobar} format descriptor!"); + fail("Expected test_ConstructorLjava_lang_String to throw IAE."); + } catch (IllegalArgumentException ex) { + // expected + } catch (Throwable ex) { + fail("Expected test_ConstructorLjava_lang_String to throw IAE, not a " + + ex.getClass().getName()); + } + + try { + new MessageFormat( + "Invalid {1,date,invalid-spec} format descriptor!"); + } catch (IllegalArgumentException ex) { + // expected + } catch (Throwable ex) { + fail("Expected test_ConstructorLjava_lang_String to throw IAE, not a " + + ex.getClass().getName()); + } + + checkSerialization(new MessageFormat("")); + checkSerialization(new MessageFormat("noargs")); + checkSerialization(new MessageFormat("{0}")); + checkSerialization(new MessageFormat("a{0}")); + checkSerialization(new MessageFormat("{0}b")); + checkSerialization(new MessageFormat("a{0}b")); + + // Regression for HARMONY-65 + try { + new MessageFormat("{0,number,integer"); + fail("Assert 0: Failed to detect unmatched brackets."); + } catch (IllegalArgumentException e) { + // expected + } + } + + /** + * @tests java.text.MessageFormat#applyPattern(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "applyPattern", + args = {java.lang.String.class} + ) + public void test_applyPatternLjava_lang_String() { + // Test for method void + // java.text.MessageFormat.applyPattern(java.lang.String) + MessageFormat format = new MessageFormat("test"); + format.applyPattern("xx {0}"); + assertEquals("Invalid number", "xx 46", format + .format(new Object[] { new Integer(46) })); + Date date = new Date(); + String result = format.format(new Object[] { date }); + String expected = "xx " + DateFormat.getInstance().format(date); + assertTrue("Invalid date:\n" + result + "\n" + expected, result + .equals(expected)); + format = new MessageFormat("{0,date}{1,time}{2,number,integer}"); + format.applyPattern("nothing"); + assertEquals("Found formats", "nothing", format.toPattern()); + + format.applyPattern("{0}"); + assertNull("Wrong format", format.getFormats()[0]); + assertEquals("Wrong pattern", "{0}", format.toPattern()); + + format.applyPattern("{0, \t\u001ftime }"); + assertTrue("Wrong time format", format.getFormats()[0] + .equals(DateFormat.getTimeInstance())); + assertEquals("Wrong time pattern", "{0,time}", format.toPattern()); + format.applyPattern("{0,Time, Short\n}"); + assertTrue("Wrong short time format", format.getFormats()[0] + .equals(DateFormat.getTimeInstance(DateFormat.SHORT))); + assertEquals("Wrong short time pattern", "{0,time,short}", format + .toPattern()); + format.applyPattern("{0,TIME,\nmedium }"); + assertTrue("Wrong medium time format", format.getFormats()[0] + .equals(DateFormat.getTimeInstance(DateFormat.MEDIUM))); + assertEquals("Wrong medium time pattern", "{0,time}", format + .toPattern()); + format.applyPattern("{0,time,LONG}"); + assertTrue("Wrong long time format", format.getFormats()[0] + .equals(DateFormat.getTimeInstance(DateFormat.LONG))); + assertEquals("Wrong long time pattern", "{0,time,long}", format + .toPattern()); + format.setLocale(Locale.FRENCH); // use French since English has the + // same LONG and FULL time patterns + format.applyPattern("{0,time, Full}"); + assertTrue("Wrong full time format", format.getFormats()[0] + .equals(DateFormat.getTimeInstance(DateFormat.FULL, + Locale.FRENCH))); +// Outsourced to _AndroidFailure: +// +// assertEquals("Wrong full time pattern", "{0,time,full}", format +// .toPattern()); + format.setLocale(Locale.getDefault()); + + format.applyPattern("{0, date}"); + assertTrue("Wrong date format", format.getFormats()[0] + .equals(DateFormat.getDateInstance())); + assertEquals("Wrong date pattern", "{0,date}", format.toPattern()); + format.applyPattern("{0, date, short}"); + assertTrue("Wrong short date format", format.getFormats()[0] + .equals(DateFormat.getDateInstance(DateFormat.SHORT))); + assertEquals("Wrong short date pattern", "{0,date,short}", format + .toPattern()); + format.applyPattern("{0, date, medium}"); + assertTrue("Wrong medium date format", format.getFormats()[0] + .equals(DateFormat.getDateInstance(DateFormat.MEDIUM))); + assertEquals("Wrong medium date pattern", "{0,date}", format + .toPattern()); + format.applyPattern("{0, date, long}"); + assertTrue("Wrong long date format", format.getFormats()[0] + .equals(DateFormat.getDateInstance(DateFormat.LONG))); + assertEquals("Wrong long date pattern", "{0,date,long}", format + .toPattern()); + format.applyPattern("{0, date, full}"); + assertTrue("Wrong full date format", format.getFormats()[0] + .equals(DateFormat.getDateInstance(DateFormat.FULL))); + assertEquals("Wrong full date pattern", "{0,date,full}", format + .toPattern()); + + format.applyPattern("{0, date, MMM d {hh:mm:ss}}"); + assertEquals("Wrong time/date format", " MMM d {hh:mm:ss}", + ((SimpleDateFormat) (format.getFormats()[0])).toPattern()); + assertEquals("Wrong time/date pattern", "{0,date, MMM d {hh:mm:ss}}", + format.toPattern()); + + format.applyPattern("{0, number}"); + assertTrue("Wrong number format", format.getFormats()[0] + .equals(NumberFormat.getNumberInstance())); + assertEquals("Wrong number pattern", "{0,number}", format.toPattern()); + format.applyPattern("{0, number, currency}"); + assertTrue("Wrong currency number format", format.getFormats()[0] + .equals(NumberFormat.getCurrencyInstance())); + assertEquals("Wrong currency number pattern", "{0,number,currency}", + format.toPattern()); + format.applyPattern("{0, number, percent}"); + assertTrue("Wrong percent number format", format.getFormats()[0] + .equals(NumberFormat.getPercentInstance())); + assertEquals("Wrong percent number pattern", "{0,number,percent}", + format.toPattern()); + format.applyPattern("{0, number, integer}"); + NumberFormat nf = NumberFormat.getInstance(); + nf.setMaximumFractionDigits(0); + nf.setParseIntegerOnly(true); + assertTrue("Wrong integer number format", format.getFormats()[0] + .equals(nf)); + assertEquals("Wrong integer number pattern", "{0,number,integer}", + format.toPattern()); + + format.applyPattern("{0, number, {'#'}##0.0E0}"); + + /* + * TODO validate these assertions String actual = + * ((DecimalFormat)(format.getFormats()[0])).toPattern(); + * assertEquals("Wrong pattern number format", "' {#}'##0.0E0", actual); + * assertEquals("Wrong pattern number pattern", "{0,number,' + * {#}'##0.0E0}", format.toPattern()); + * + */ + + format.applyPattern("{0, choice,0#no|1#one|2#{1,number}}"); + assertEquals("Wrong choice format", + + "0.0#no|1.0#one|2.0#{1,number}", + ((ChoiceFormat) format.getFormats()[0]).toPattern()); + assertEquals("Wrong choice pattern", + "{0,choice,0.0#no|1.0#one|2.0#{1,number}}", format.toPattern()); + assertEquals("Wrong formatted choice", "3.6", format + .format(new Object[] { new Integer(2), new Float(3.6) })); + + try { + format.applyPattern("WRONG MESSAGE FORMAT {0,number,{}"); + fail("Expected IllegalArgumentException for invalid pattern"); + } catch (IllegalArgumentException e) { + } + + // Regression for HARMONY-65 + MessageFormat mf = new MessageFormat("{0,number,integer}"); + String badpattern = "{0,number,#"; + try { + mf.applyPattern(badpattern); + fail("Assert 0: Failed to detect unmatched brackets."); + } catch (IllegalArgumentException e) { + // expected + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "applyPattern", + args = {java.lang.String.class} + ) + @KnownFailure("Succeeds against RI.") + public void test_applyPatternLjava_lang_String_AndroidFailure() { + MessageFormat format = new MessageFormat("test"); + format.setLocale(Locale.FRENCH); // use French since English has the + // same LONG and FULL time patterns + format.applyPattern("{0,time, Full}"); + assertEquals("Wrong full time pattern", "{0,time,full}", format + .toPattern()); + } + + /** + * @tests java.text.MessageFormat#clone() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + // Test for method java.lang.Object java.text.MessageFormat.clone() + MessageFormat format = new MessageFormat("'{'choice'}'{0}"); + MessageFormat clone = (MessageFormat) format.clone(); + assertTrue("Clone not equal", format.equals(clone)); + assertEquals("Wrong answer", "{choice}{0}", format + .format(new Object[] {})); + clone.setFormat(0, DateFormat.getInstance()); + assertTrue("Clone shares format data", !format.equals(clone)); + format = (MessageFormat) clone.clone(); + Format[] formats = clone.getFormats(); + ((SimpleDateFormat) formats[0]).applyPattern("adk123"); + assertTrue("Clone shares format data", !format.equals(clone)); + } + + /** + * @tests java.text.MessageFormat#equals(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + // Test for method boolean + // java.text.MessageFormat.equals(java.lang.Object) + MessageFormat format1 = new MessageFormat("{0}"); + MessageFormat format2 = new MessageFormat("{1}"); + assertTrue("Should not be equal", !format1.equals(format2)); + format2.applyPattern("{0}"); + assertTrue("Should be equal", format1.equals(format2)); + SimpleDateFormat date = (SimpleDateFormat) DateFormat.getTimeInstance(); + format1.setFormat(0, DateFormat.getTimeInstance()); + format2.setFormat(0, new SimpleDateFormat(date.toPattern())); + assertTrue("Should be equal2", format1.equals(format2)); + } + + /** + * @tests java.text.MessageFormat#hashCode() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + // Test for method + // int java.text.MessageFormat.hashCode() + assertEquals("Should be equal", 3648, new MessageFormat("rr", null) + .hashCode()); + } + + /** + * @tests java.text.MessageFormat#formatToCharacterIterator(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "formatToCharacterIterator", + args = {java.lang.Object.class} + ) + // FIXME This test fails on Harmony ClassLibrary + public void test_formatToCharacterIteratorLjava_lang_Object() { + // Test for method formatToCharacterIterator(java.lang.Object) + new Support_MessageFormat( + "test_formatToCharacterIteratorLjava_lang_Object") + .t_formatToCharacterIterator(); + + try { + new MessageFormat("{1, number}").formatToCharacterIterator(null); + fail("NullPointerException was not thrown."); + } catch(NullPointerException npe) { + //expected + } + + try { + new MessageFormat("{0, time}").formatToCharacterIterator(new Object[]{""}); + fail("IllegalArgumentException was not thrown."); + } catch(IllegalArgumentException iae) { + //expected + } + } + + /** + * @tests java.text.MessageFormat#format(java.lang.Object[], + * java.lang.StringBuffer, java.text.FieldPosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {java.lang.Object[].class, java.lang.StringBuffer.class, java.text.FieldPosition.class} + ) + public void test_format$Ljava_lang_ObjectLjava_lang_StringBufferLjava_text_FieldPosition() { + // Test for method java.lang.StringBuffer + // java.text.MessageFormat.format(java.lang.Object [], + // java.lang.StringBuffer, java.text.FieldPosition) + MessageFormat format = new MessageFormat("{1,number,integer}"); + StringBuffer buffer = new StringBuffer(); + format.format(new Object[] { "0", new Double(53.863) }, buffer, + new FieldPosition(MessageFormat.Field.ARGUMENT)); + assertEquals("Wrong result", "54", buffer.toString()); + + format.format(new Object[] { "0", new Double(53.863) }, buffer, + new FieldPosition(MessageFormat.Field.ARGUMENT)); + + assertEquals("Wrong result", "5454", buffer.toString()); + + buffer = new StringBuffer(); + format + .applyPattern("{0,choice,0#zero|1#one '{1,choice,2#two {2,time}}'}"); + Date date = new Date(); + String expected = "one two " + + DateFormat.getTimeInstance().format(date); + format.format(new Object[] { new Double(1.6), + new Integer(3), date }, buffer, new FieldPosition(MessageFormat + .Field.ARGUMENT)); + assertEquals("Choice not recursive:\n" + expected + "\n" + buffer, + expected, buffer.toString()); + + StringBuffer str = format.format(new Object[] { new Double(0.6), + new Integer(3)}, buffer, null); + + assertEquals(expected + "zero", str.toString()); + assertEquals(expected + "zero", buffer.toString()); + + try { + format.format(new Object[] { "0", new Double(1), "" }, buffer, + new FieldPosition(MessageFormat.Field.ARGUMENT)); + fail("IllegalArgumentException was not thrown."); + } catch(IllegalArgumentException iae) { + //expected + } + + try { + format.format(new Object[] { "", new Integer(3)}, buffer, + new FieldPosition(MessageFormat.Field.ARGUMENT)); + fail("IllegalArgumentException was not thrown."); + } catch(IllegalArgumentException iae) { + //expected + } + } + + /** + * @tests java.text.MessageFormat#format(java.lang.Object, + * java.lang.StringBuffer, java.text.FieldPosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {java.lang.Object.class, java.lang.StringBuffer.class, java.text.FieldPosition.class} + ) + public void test_formatLjava_lang_ObjectLjava_lang_StringBufferLjava_text_FieldPosition() { + // Test for method java.lang.StringBuffer + // java.text.MessageFormat.format(java.lang.Object, + // java.lang.StringBuffer, java.text.FieldPosition) + new Support_MessageFormat( + "test_formatLjava_lang_ObjectLjava_lang_StringBufferLjava_text_FieldPosition") + .t_format_with_FieldPosition(); + + String pattern = "On {4,date} at {3,time}, he ate {2,number, integer} " + + "hamburger{2,choice,1#|1<s}."; + MessageFormat format = new MessageFormat(pattern, Locale.US); + + Object[] objects = new Object[] { "", new Integer(3), 8, ""}; + + try { + format.format(objects, new StringBuffer(), + new FieldPosition(DateFormat.Field.AM_PM)); + fail("IllegalArgumentException was not thrown."); + } catch(IllegalArgumentException iae) { + //expected + } + } + + /** + * @tests java.text.MessageFormat#format(java.lang.String, + * java.lang.Object...) Test of method + * java.text.MessageFormat#format(java.lang.String, + * java.lang.Object...). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {java.lang.String.class, java.lang.Object[].class} + ) + public void test_formatLjava_lang_StringLjava_lang_Object() { + int iCurrency = 123; + int iInteger = Integer.MIN_VALUE; + + Date date = new Date(12345678); + Object[] args = { date, iCurrency, iInteger }; + String resStr = "Date: Jan 1, 1970 Currency: $" + iCurrency + + ".00 Integer: -2,147,483,648"; + String pattern = "Date: {0,date} Currency: {1, number, currency} Integer: {2, number, integer}"; + String sFormat = MessageFormat.format(pattern, (Object[]) args); + assertEquals( + "format(String, Object[]) with valid parameters returns incorrect string: case 1", + sFormat, resStr); + + pattern = "abc {4, number, integer} def {3,date} ghi {2,number} jkl {1,choice,0#low|1#high} mnop {0}"; + resStr = "abc -2,147,483,648 def Jan 1, 1970 ghi -2,147,483,648 jkl high mnop -2,147,483,648"; + Object[] args_ = { iInteger, 1, iInteger, date, iInteger }; + sFormat = MessageFormat.format(pattern, args_); + assertEquals( + "format(String, Object[]) with valid parameters returns incorrect string: case 1", + sFormat, resStr); + + try { + args = null; + MessageFormat.format(null, args); + fail("Doesn't throw IllegalArgumentException: null, null"); + } catch (Exception e) { + // expected + } + + try { + MessageFormat.format("Invalid {1,foobar} format descriptor!", + new Object[] {iInteger} ); + fail("Doesn't throw IllegalArgumentException with invalid pattern as a parameter: case 1"); + } catch (IllegalArgumentException ex) { + // expected + } + + try { + MessageFormat.format( + "Invalid {1,date,invalid-spec} format descriptor!", new Object[]{""}); + fail("Doesn't throw IllegalArgumentException with invalid pattern as a parameter: case 2"); + } catch (IllegalArgumentException ex) { + // expected + } + + try { + MessageFormat.format("{0,number,integer", new Object[] {iInteger}); + fail("Doesn't throw IllegalArgumentException, doesn't detect unmatched brackets"); + } catch (IllegalArgumentException ex) { + // expected + } + + try { + MessageFormat.format( + "Valid {1, date} format {0, number} descriptor!", new Object[]{ "" } ); + fail("Doesn't throw IllegalArgumentException with invalid Object array"); + } catch (IllegalArgumentException ex) { + // expected + } + } + + /** + * @tests java.text.MessageFormat#getFormats() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getFormats", + args = {} + ) + public void test_getFormats() { + // Test for method java.text.Format [] + // java.text.MessageFormat.getFormats() + + // test with repeating formats and max argument index < max offset + Format[] formats = format1.getFormats(); + Format[] correctFormats = new Format[] { + NumberFormat.getCurrencyInstance(), + DateFormat.getTimeInstance(), + NumberFormat.getPercentInstance(), null, + new ChoiceFormat("0#off|1#on"), DateFormat.getDateInstance(), }; + + assertEquals("Test1:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test1:wrong format for pattern index " + i + ":", + correctFormats[i], formats[i]); + } + + // test with max argument index > max offset + formats = format2.getFormats(); + correctFormats = new Format[] { NumberFormat.getCurrencyInstance(), + DateFormat.getTimeInstance(), + NumberFormat.getPercentInstance(), null, + new ChoiceFormat("0#off|1#on"), DateFormat.getDateInstance() }; + + assertEquals("Test2:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test2:wrong format for pattern index " + i + ":", + correctFormats[i], formats[i]); + } + + // test with argument number being zero + formats = format3.getFormats(); + assertEquals("Test3: Returned wrong number of formats:", 0, + formats.length); + } + + /** + * @tests java.text.MessageFormat#getFormatsByArgumentIndex() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getFormatsByArgumentIndex", + args = {} + ) + public void test_getFormatsByArgumentIndex() { + // Test for method java.text.Format [] test_getFormatsByArgumentIndex() + + // test with repeating formats and max argument index < max offset + Format[] formats = format1.getFormatsByArgumentIndex(); + Format[] correctFormats = new Format[] { DateFormat.getDateInstance(), + new ChoiceFormat("0#off|1#on"), DateFormat.getTimeInstance(), + NumberFormat.getCurrencyInstance(), null }; + + assertEquals("Test1:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test1:wrong format for argument index " + i + ":", + correctFormats[i], formats[i]); + } + + // test with max argument index > max offset + formats = format2.getFormatsByArgumentIndex(); + correctFormats = new Format[] { DateFormat.getDateInstance(), + new ChoiceFormat("0#off|1#on"), null, + NumberFormat.getCurrencyInstance(), null, null, null, null, + DateFormat.getTimeInstance() }; + + assertEquals("Test2:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test2:wrong format for argument index " + i + ":", + correctFormats[i], formats[i]); + } + + // test with argument number being zero + formats = format3.getFormatsByArgumentIndex(); + assertEquals("Test3: Returned wrong number of formats:", 0, + formats.length); + } + + /** + * @tests java.text.MessageFormat#getLocale() Test of method + * java.text.MessageFormat#getLocale(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getLocale", + args = {} + ) + public void test_getLocale() { + try { + Locale[] l = { + Locale.FRANCE, + Locale.KOREA, + new Locale(Locale.FRANCE.getCountry(), Locale.FRANCE + .getLanguage()), new Locale("mk"), + new Locale("mk", "MK"), Locale.US, + new Locale("#ru", "@31230") }; + + String pattern = "getLocale test {0,number,#,####}"; + MessageFormat mf; + + for (int i = 0; i < 0; i++) { + mf = new MessageFormat(pattern, l[i]); + Locale result = mf.getLocale(); + assertEquals("Returned local: " + result + " instead of " + + l[i], l[i], result); + assertEquals("Returned language: " + result.getLanguage() + + " instead of " + l[i].getLanguage(), l[i] + .getLanguage(), result.getLanguage()); + assertEquals("Returned country: " + result.getCountry() + + " instead of " + l[i].getCountry(), + l[i].getCountry(), result.getCountry()); + } + + mf = new MessageFormat(pattern); + mf.setLocale(null); + Locale result = mf.getLocale(); + assertEquals("Returned local: " + result + " instead of null", + null, result); + } catch (Exception e) { + fail("unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.MessageFormat#setFormat(int, Format) Test of method + * java.text.MessageFormat#setFormat(int, Format). Case 1: Compare + * getFormats() results after calls to setFormat(). Case 2: Try to + * call setFormat() using incorrect index. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setFormat", + args = {int.class, java.text.Format.class} + ) + public void test_setFormatILjava_text_Format() { + try { + // case 1: Compare getFormats() results after calls to setFormat() + MessageFormat f1 = (MessageFormat) format1.clone(); + f1.setFormat(0, DateFormat.getTimeInstance()); + f1.setFormat(1, DateFormat.getTimeInstance()); + f1.setFormat(2, NumberFormat.getInstance()); + f1.setFormat(3, new ChoiceFormat("0#off|1#on")); + f1.setFormat(4, new ChoiceFormat("1#few|2#ok|3#a lot")); + f1.setFormat(5, DateFormat.getTimeInstance()); + + Format[] formats = f1.getFormats(); + formats = f1.getFormats(); + + Format[] correctFormats = new Format[] { + DateFormat.getTimeInstance(), DateFormat.getTimeInstance(), + NumberFormat.getInstance(), new ChoiceFormat("0#off|1#on"), + new ChoiceFormat("1#few|2#ok|3#a lot"), + DateFormat.getTimeInstance() }; + + assertEquals("Test1A:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals( + "Test1B:wrong format for pattern index " + i + ":", + correctFormats[i], formats[i]); + } + + // case 2: Try to setFormat using incorrect index + try { + f1.setFormat(-1, DateFormat.getDateInstance()); + fail("Expected ArrayIndexOutOfBoundsException was not thrown"); + f1.setFormat(f1.getFormats().length, DateFormat + .getDateInstance()); + fail("Expected ArrayIndexOutOfBoundsException was not thrown"); + } catch (ArrayIndexOutOfBoundsException e) { + // expected + } + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.MessageFormat#setFormatByArgumentIndex(int, + * java.text.Format) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setFormatByArgumentIndex", + args = {int.class, java.text.Format.class} + ) + public void test_setFormatByArgumentIndexILjava_text_Format() { + // test for method setFormatByArgumentIndex(int, Format) + MessageFormat f1 = (MessageFormat) format1.clone(); + f1.setFormatByArgumentIndex(0, DateFormat.getTimeInstance()); + f1.setFormatByArgumentIndex(4, new ChoiceFormat("1#few|2#ok|3#a lot")); + + // test with repeating formats and max argument index < max offset + // compare getFormatsByArgumentIndex() results after calls to + // setFormatByArgumentIndex() + Format[] formats = f1.getFormatsByArgumentIndex(); + + Format[] correctFormats = new Format[] { DateFormat.getTimeInstance(), + new ChoiceFormat("0#off|1#on"), DateFormat.getTimeInstance(), + NumberFormat.getCurrencyInstance(), + new ChoiceFormat("1#few|2#ok|3#a lot") }; + + assertEquals("Test1A:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test1B:wrong format for argument index " + i + ":", + correctFormats[i], formats[i]); + } + + // compare getFormats() results after calls to + // setFormatByArgumentIndex() + formats = f1.getFormats(); + + correctFormats = new Format[] { NumberFormat.getCurrencyInstance(), + DateFormat.getTimeInstance(), DateFormat.getTimeInstance(), + new ChoiceFormat("1#few|2#ok|3#a lot"), + new ChoiceFormat("0#off|1#on"), DateFormat.getTimeInstance(), }; + + assertEquals("Test1C:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test1D:wrong format for pattern index " + i + ":", + correctFormats[i], formats[i]); + } + + // test setting argumentIndexes that are not used + MessageFormat f2 = (MessageFormat) format2.clone(); + f2.setFormatByArgumentIndex(2, NumberFormat.getPercentInstance()); + f2.setFormatByArgumentIndex(4, DateFormat.getTimeInstance()); + + formats = f2.getFormatsByArgumentIndex(); + correctFormats = format2.getFormatsByArgumentIndex(); + + assertEquals("Test2A:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test2B:wrong format for argument index " + i + ":", + correctFormats[i], formats[i]); + } + + formats = f2.getFormats(); + correctFormats = format2.getFormats(); + + assertEquals("Test2C:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test2D:wrong format for pattern index " + i + ":", + correctFormats[i], formats[i]); + } + + // test exceeding the argumentIndex number + MessageFormat f3 = (MessageFormat) format3.clone(); + f3.setFormatByArgumentIndex(1, NumberFormat.getCurrencyInstance()); + + formats = f3.getFormatsByArgumentIndex(); + assertEquals("Test3A:Returned wrong number of formats:", 0, + formats.length); + + formats = f3.getFormats(); + assertEquals("Test3B:Returned wrong number of formats:", 0, + formats.length); + } + + /** + * @tests java.text.MessageFormat#setFormats(Format[]) Test of method + * java.text.MessageFormat#setFormats(Format[]). Case 1: Test with + * repeating formats and max argument index < max offset compare + * getFormats() results after calls to setFormats(Format[]) Case 2: + * Try to pass null argument to setFormats(). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setFormats", + args = {java.text.Format[].class} + ) + public void test_setFormats$Ljava_text_Format() { + try { + MessageFormat f1 = (MessageFormat) format1.clone(); + + // case 1: Test with repeating formats and max argument index < max + // offset + // compare getFormats() results after calls to setFormats(Format[]) + Format[] correctFormats = new Format[] { + DateFormat.getTimeInstance(), + new ChoiceFormat("0#off|1#on"), + DateFormat.getTimeInstance(), + NumberFormat.getCurrencyInstance(), + new ChoiceFormat("1#few|2#ok|3#a lot") }; + + f1.setFormats(correctFormats); + Format[] formats = f1.getFormats(); + + assertTrue("Test1A:Returned wrong number of formats:", + correctFormats.length <= formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test1B:wrong format for argument index " + i + + ":", correctFormats[i], formats[i]); + } + + // case 2: Try to pass null argument to setFormats(). + try { + f1.setFormats(null); + fail("Expected exception NullPointerException was not thrown"); + } catch (NullPointerException e) { + // expected + } + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + + /** + * @tests java.text.MessageFormat#setFormatsByArgumentIndex(java.text.Format[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setFormatsByArgumentIndex", + args = {java.text.Format[].class} + ) + public void test_setFormatsByArgumentIndex$Ljava_text_Format() { + // test for method setFormatByArgumentIndex(Format[]) + MessageFormat f1 = (MessageFormat) format1.clone(); + + // test with repeating formats and max argument index < max offset + // compare getFormatsByArgumentIndex() results after calls to + // setFormatsByArgumentIndex(Format[]) + Format[] correctFormats = new Format[] { DateFormat.getTimeInstance(), + new ChoiceFormat("0#off|1#on"), DateFormat.getTimeInstance(), + NumberFormat.getCurrencyInstance(), + new ChoiceFormat("1#few|2#ok|3#a lot") }; + + f1.setFormatsByArgumentIndex(correctFormats); + Format[] formats = f1.getFormatsByArgumentIndex(); + + assertEquals("Test1A:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test1B:wrong format for argument index " + i + ":", + correctFormats[i], formats[i]); + } + + // compare getFormats() results after calls to + // setFormatByArgumentIndex() + formats = f1.getFormats(); + correctFormats = new Format[] { NumberFormat.getCurrencyInstance(), + DateFormat.getTimeInstance(), DateFormat.getTimeInstance(), + new ChoiceFormat("1#few|2#ok|3#a lot"), + new ChoiceFormat("0#off|1#on"), DateFormat.getTimeInstance(), }; + + assertEquals("Test1C:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test1D:wrong format for pattern index " + i + ":", + correctFormats[i], formats[i]); + } + + // test setting argumentIndexes that are not used + MessageFormat f2 = (MessageFormat) format2.clone(); + Format[] inputFormats = new Format[] { DateFormat.getDateInstance(), + new ChoiceFormat("0#off|1#on"), + NumberFormat.getPercentInstance(), + NumberFormat.getCurrencyInstance(), + DateFormat.getTimeInstance(), null, null, null, + DateFormat.getTimeInstance() }; + f2.setFormatsByArgumentIndex(inputFormats); + + formats = f2.getFormatsByArgumentIndex(); + correctFormats = format2.getFormatsByArgumentIndex(); + + assertEquals("Test2A:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test2B:wrong format for argument index " + i + ":", + correctFormats[i], formats[i]); + } + + formats = f2.getFormats(); + correctFormats = new Format[] { NumberFormat.getCurrencyInstance(), + DateFormat.getTimeInstance(), DateFormat.getDateInstance(), + null, new ChoiceFormat("0#off|1#on"), + DateFormat.getDateInstance() }; + + assertEquals("Test2C:Returned wrong number of formats:", + correctFormats.length, formats.length); + for (int i = 0; i < correctFormats.length; i++) { + assertEquals("Test2D:wrong format for pattern index " + i + ":", + correctFormats[i], formats[i]); + } + + // test exceeding the argumentIndex number + MessageFormat f3 = (MessageFormat) format3.clone(); + f3.setFormatsByArgumentIndex(inputFormats); + + formats = f3.getFormatsByArgumentIndex(); + assertEquals("Test3A:Returned wrong number of formats:", 0, + formats.length); + + formats = f3.getFormats(); + assertEquals("Test3B:Returned wrong number of formats:", 0, + formats.length); + + } + + /** + * @tests java.text.MessageFormat#parse(java.lang.String, + * java.text.ParsePosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "parse", + args = {java.lang.String.class, java.text.ParsePosition.class} + ) + public void test_parseLjava_lang_StringLjava_text_ParsePosition() { + // Test for method java.lang.Object [] + // java.text.MessageFormat.parse(java.lang.String, + // java.text.ParsePosition) + MessageFormat format = new MessageFormat("date is {0,date,MMM d, yyyy}"); + ParsePosition pos = new ParsePosition(2); + Object[] result = (Object[]) format + .parse("xxdate is Feb 28, 1999", pos); + assertTrue("No result: " + result.length, result.length >= 1); + assertTrue("Wrong answer", ((Date) result[0]) + .equals(new GregorianCalendar(1999, Calendar.FEBRUARY, 28) + .getTime())); + + MessageFormat mf = new MessageFormat("vm={0},{1},{2}"); + result = mf.parse("vm=win,foo,bar", new ParsePosition(0)); + assertTrue("Invalid parse", result[0].equals("win") + && result[1].equals("foo") && result[2].equals("bar")); + + mf = new MessageFormat("{0}; {0}; {0}"); + String parse = "a; b; c"; + result = mf.parse(parse, new ParsePosition(0)); + assertEquals("Wrong variable result", "c", result[0]); + + try { + mf.parse(parse, null); + fail("NullPointerException was not thrown."); + } catch(NullPointerException npe) { + //expected + } + + try { + mf.parse(null, pos); + } catch(NullPointerException npe) { + fail("NullPointerException was thrown."); + } + } + + /** + * @tests java.text.MessageFormat#setLocale(java.util.Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setLocale", + args = {java.util.Locale.class} + ) + public void test_setLocaleLjava_util_Locale() { + // Test for method void + // java.text.MessageFormat.setLocale(java.util.Locale) + MessageFormat format = new MessageFormat("date {0,date}"); + format.setLocale(Locale.CHINA); + assertEquals("Wrong locale1", Locale.CHINA, format.getLocale()); + format.applyPattern("{1,date}"); + assertEquals("Wrong locale3", DateFormat.getDateInstance( + DateFormat.DEFAULT, Locale.CHINA), format.getFormats()[0]); + } + + /** + * @tests java.text.MessageFormat#toPattern() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "toPattern", + args = {} + ) + public void test_toPattern() { + // Test for method java.lang.String java.text.MessageFormat.toPattern() + String pattern = "[{0}]"; + MessageFormat mf = new MessageFormat(pattern); + assertTrue("Wrong pattern", mf.toPattern().equals(pattern)); + + // Regression for HARMONY-59 + new MessageFormat("CHOICE {1,choice}").toPattern(); + } + + /** + * Sets up the fixture, for example, open a network connection. This method + * is called before a test is executed. + */ + protected void setUp() { + defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + + // test with repeating formats and max argument index < max offset + String pattern = "A {3, number, currency} B {2, time} C {0, number, percent} D {4} E {1,choice,0#off|1#on} F {0, date}"; + format1 = new MessageFormat(pattern); + + // test with max argument index > max offset + pattern = "A {3, number, currency} B {8, time} C {0, number, percent} D {6} E {1,choice,0#off|1#on} F {0, date}"; + format2 = new MessageFormat(pattern); + + // test with argument number being zero + pattern = "A B C D E F"; + format3 = new MessageFormat(pattern); + } + + /** + * Tears down the fixture, for example, close a network connection. This + * method is called after a test is executed. + */ + protected void tearDown() { + Locale.setDefault(defaultLocale); + } + + /** + * @tests java.text.MessageFormat(java.util.Locale) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Verifies IllegalArgumentException.", + method = "MessageFormat", + args = {java.lang.String.class, java.util.Locale.class} + ) + public void test_ConstructorLjava_util_Locale() { + // Regression for HARMONY-65 + try { + new MessageFormat("{0,number,integer", Locale.US); + fail("Assert 0: Failed to detect unmatched brackets."); + } catch (IllegalArgumentException e) { + // expected + } + } + + /** + * @tests java.text.MessageFormat#parse(java.lang.String) Test of method + * java.text.MessageFormat#parse(java.lang.String). + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "parse", + args = {java.lang.String.class} + ) + public void test_parseLjava_lang_String() throws ParseException { + String pattern = "A {3, number, currency} B {2, time} C {0, number, percent} D {4} E {1,choice,0#off|1#on} F {0, date}"; + MessageFormat mf = new MessageFormat(pattern); + String sToParse = "A $12,345.00 B 9:56:07 AM C 3,200% D 1/15/70 9:56 AM E on F Jan 1, 1970"; + Object[] result; + try { + result = mf.parse(sToParse); + + assertTrue("No result: " + result.length, result.length == 5); + assertTrue("Object 0 is not date", result[0] instanceof Date); + assertEquals("Object 1 is not stringr", result[1].toString(), "1.0"); + assertTrue("Object 2 is not date", result[2] instanceof Date); + assertEquals("Object 3 is not number", result[3].toString(), + "12345"); + assertEquals("Object 4 is not string", result[4].toString(), + "1/15/70 9:56 AM"); + + } catch (java.text.ParseException pe) { + fail("ParseException is thrown for incorrect string " + sToParse); + } + + sToParse = "xxdate is Feb 28, 1999"; + try { + result = format1.parse(sToParse); + fail("ParseException is thrown for incorrect string " + sToParse); + } catch (java.text.ParseException pe) { + // expected + } + + sToParse = "vm=Test, @3 4 6, 3 "; + mf = new MessageFormat("vm={0},{1},{2}"); + try { + result = mf.parse(sToParse); + assertTrue("No result: " + result.length, result.length == 3); + assertEquals("Object 0 is not string", result[0].toString(), "Test"); + assertEquals("Object 1 is not string", result[1].toString(), + " @3 4 6"); + assertEquals("Object 2 is not string", result[2].toString(), + " 3 "); + } catch (java.text.ParseException pe) { + fail("ParseException is thrown for correct string " + sToParse); + } + + try { + result = mf.parse(null); + fail("ParseException is not thrown for null " + sToParse); + } catch (java.text.ParseException pe) { + // expected + } + } + + /** + * @tests java.text.MessageFormat#parseObject(java.lang.String, + * java.text.ParsePosition) Test of method + * java.text.MessageFormat#parseObject(java.lang.String, + * java.text.ParsePosition). Case 1: Parsing of correct data string. + * Case 2: Parsing of partial correct data string. Case 3: Try to use + * argument ParsePosition as null. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "parseObject", + args = {java.lang.String.class, java.text.ParsePosition.class} + ) + public void test_parseObjectLjava_lang_StringLjavajava_text_ParsePosition() { + MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}"); + try { + // case 1: Try to parse correct data string. + Object[] objs = { new Double(3.1415) }; + String result = mf.format(objs); + // result now equals "3.14, 3.1" + Object[] res = null; + ParsePosition pp = new ParsePosition(0); + int parseIndex = pp.getIndex(); + res = (Object[]) mf.parseObject(result, pp); + assertTrue("Parse operation return null", res != null); + assertTrue("parse operation return array with incorrect length", + 1 == res.length); + assertTrue("ParseIndex is incorrect", pp.getIndex() != parseIndex); + assertTrue("Result object is incorrect", new Double(3.1) + .equals(res[0])); + + // case 2: Try to parse partially correct data string. + pp.setIndex(0); + char[] cur = result.toCharArray(); + cur[cur.length / 2] = 'Z'; + String partialCorrect = new String(cur); + res = (Object[]) mf.parseObject(partialCorrect, pp); + assertTrue("Parse operation return null", res == null); + assertTrue("ParseIndex is incorrect", pp.getIndex() == 0); + assertTrue("ParseErrorIndex is incorrect", + pp.getErrorIndex() == cur.length / 2); + + // case 3: Try to use argument ParsePosition as null. + try { + mf.parseObject(result, null); + fail("Expected NullPointerException was not thrown"); + } catch (NullPointerException e) { + // expected + } + } catch (Exception e) { + fail("Unexpected exception " + e.toString()); + } + } + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test. Doesn't verifies exception.", + method = "format", + args = {java.lang.Object.class} + ) + public void test_format_Object() { + // Regression for HARMONY-1875 + Locale.setDefault(Locale.CANADA); + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + String pat = "text here {0,date,yyyyyyyyy} and here"; + Calendar c = Calendar.getInstance(); + String etalon = "text here 00000" + c.get(Calendar.YEAR) + " and here"; + MessageFormat obj = new MessageFormat(pat); + assertEquals(etalon, obj.format(new Object[] { new Date() })); + } + +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/NumberFormatFieldTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/NumberFormatFieldTest.java new file mode 100644 index 0000000..1031b44 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/NumberFormatFieldTest.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.NumberFormat; + +@TestTargetClass(NumberFormat.Field.class) +public class NumberFormatFieldTest extends junit.framework.TestCase { + /** + * @tests java.text.NumberFormat$Field#Field(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Field", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() { + // protected constructor + String name = "new number format"; + MyNumberFormat field = new MyNumberFormat(name); + assertEquals("field has wrong name", name, field.getName()); + + field = new MyNumberFormat(null); + assertEquals("field has wrong name", null, field.getName()); + } + + /** + * @tests java.text.NumberFormat$Field#readResolve() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "readResolve", + args = {} + ) + public void test_readResolve() { + // test for method java.lang.Object readResolve() + + // see serialization stress tests: + // implemented in + // SerializationStressTest4.test_writeObject_NumberFormat_Field() + ObjectOutputStream out = null; + ObjectInputStream in = null; + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + out = new ObjectOutputStream(bytes); + + NumberFormat.Field nfield, nfield2; + MyNumberFormat field; + + nfield = NumberFormat.Field.CURRENCY; + + field = new MyNumberFormat(null); + + out.writeObject(nfield); + out.writeObject(field); + + in = new ObjectInputStream(new ByteArrayInputStream(bytes + .toByteArray())); + try { + nfield2 = (NumberFormat.Field) in.readObject(); + assertSame("resolved incorrectly", nfield, nfield2); + } catch (IllegalArgumentException e) { + fail("Unexpected IllegalArgumentException: " + e); + } + + try { + in.readObject(); + fail("Expected InvalidObjectException for subclass instance with null name"); + } catch (InvalidObjectException e) { + } + + } catch (IOException e) { + fail("unexpected IOException" + e); + } catch (ClassNotFoundException e) { + fail("unexpected ClassNotFoundException" + e); + } finally { + try { + if (out != null) + out.close(); + if (in != null) + in.close(); + } catch (IOException e) { + } + } + } + + static class MyNumberFormat extends NumberFormat.Field { + static final long serialVersionUID = 1L; + + static boolean flag = false; + + protected MyNumberFormat(String attr) { + super(attr); + } + + protected String getName() { + return super.getName(); + } + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/NumberFormatTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/NumberFormatTest.java new file mode 100644 index 0000000..dc6304e --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/NumberFormatTest.java @@ -0,0 +1,1392 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.text.ChoiceFormat; +import java.text.DecimalFormat; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Currency; +import java.util.Locale; + +@TestTargetClass(NumberFormat.class) +public class NumberFormatTest extends TestCase { + + /** + * @tests java.text.NumberFormat#format(java.lang.Object, + * java.lang.StringBuffer, java.text.FieldPosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {java.lang.Object.class, java.lang.StringBuffer.class, java.text.FieldPosition.class} + ) + public void test_formatLjava_lang_ObjectLjava_lang_StringBufferLjava_text_FieldPosition() { + FieldPosition pos; + StringBuffer out; + DecimalFormat format = (DecimalFormat) NumberFormat + .getInstance(Locale.US); + + pos = new FieldPosition(0); + out = format.format(new Long(Long.MAX_VALUE), new StringBuffer(), pos); + assertEquals("Wrong result L1: " + out, "9,223,372,036,854,775,807", + out.toString()); + + pos = new FieldPosition(0); + out = format.format(new Long(Long.MIN_VALUE), new StringBuffer(), pos); + assertEquals("Wrong result L2: " + out, "-9,223,372,036,854,775,808", + out.toString()); + + pos = new FieldPosition(0); + out = format.format(new java.math.BigInteger(String + .valueOf(Long.MAX_VALUE)), new StringBuffer(), pos); + assertEquals("Wrong result BI1: " + out, "9,223,372,036,854,775,807", + out.toString()); + + pos = new FieldPosition(0); + out = format.format(new java.math.BigInteger(String + .valueOf(Long.MIN_VALUE)), new StringBuffer(), pos); + assertEquals("Wrong result BI2: " + out, "-9,223,372,036,854,775,808", + out.toString()); + + java.math.BigInteger big; + pos = new FieldPosition(0); + big = new java.math.BigInteger(String.valueOf(Long.MAX_VALUE)) + .add(new java.math.BigInteger("1")); + out = format.format(big, new StringBuffer(), pos); + assertEquals("Wrong result BI3: " + out, "9,223,372,036,854,775,808", + out.toString()); + + pos = new FieldPosition(0); + big = new java.math.BigInteger(String.valueOf(Long.MIN_VALUE)) + .add(new java.math.BigInteger("-1")); + out = format.format(big, new StringBuffer(), pos); + assertEquals("Wrong result BI4: " + out, "-9,223,372,036,854,775,809", + out.toString()); + + pos = new FieldPosition(0); + out = format.format(new java.math.BigDecimal("51.348"), + new StringBuffer(), pos); + assertEquals("Wrong result BD1: " + out, "51.348", out.toString()); + + pos = new FieldPosition(0); + out = format.format(new java.math.BigDecimal("51"), new StringBuffer(), + pos); + assertEquals("Wrong result BD2: " + out, "51", out.toString()); + + } + + /** + * @tests java.text.NumberFormat#getIntegerInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getIntegerInstance", + args = {} + ) + public void test_getIntegerInstance() throws ParseException { + // Test for method java.text.NumberFormat getIntegerInstance() + Locale origLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + + DecimalFormat format = (DecimalFormat) NumberFormat + .getIntegerInstance(); + + assertEquals( + "Test1: NumberFormat.getIntegerInstance().toPattern() returned wrong pattern", + "#,##0", format.toPattern()); + assertEquals( + "Test2: NumberFormat.getIntegerInstance().format(35.76) returned wrong value", + "36", format.format(35.76)); + assertEquals( + "Test3: NumberFormat.getIntegerInstance().parse(\"35.76\") returned wrong number", + new Long(35), format.parse("35.76")); + assertEquals( + "Test4: NumberFormat.getIntegerInstance().parseObject(\"35.76\") returned wrong number", + new Long(35), format.parseObject("35.76")); + Locale.setDefault(origLocale); + } + + /** + * @tests java.text.NumberFormat#getIntegerInstance(java.util.Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getIntegerInstance", + args = {java.util.Locale.class} + ) + public void test_getIntegerInstanceLjava_util_Locale() + throws ParseException { + // Test for method java.text.NumberFormat + // getIntegerInstance(java.util.Locale) + Locale usLocale = Locale.US; + // BEGIN android-changed + // use de_CH instead + // Locale arLocale = new Locale("ar", "AE"); + Locale chLocale = new Locale("de", "CH"); + // END android-changed + + DecimalFormat format = (DecimalFormat) NumberFormat + .getIntegerInstance(usLocale); + assertEquals( + "Test1: NumberFormat.getIntegerInstance().toPattern() returned wrong pattern", + "#,##0", format.toPattern()); + assertEquals( + "Test2: NumberFormat.getIntegerInstance().format(-35.76) returned wrong value", + "-36", format.format(-35.76)); + assertEquals( + "Test3: NumberFormat.getIntegerInstance().parse(\"-36\") returned wrong number", + new Long(-36), format.parse("-36")); + assertEquals( + "Test4: NumberFormat.getIntegerInstance().parseObject(\"-36\") returned wrong number", + new Long(-36), format.parseObject("-36")); + assertEquals( + "Test5: NumberFormat.getIntegerInstance().getMaximumFractionDigits() returned wrong value", + 0, format.getMaximumFractionDigits()); + assertTrue( + "Test6: NumberFormat.getIntegerInstance().isParseIntegerOnly() returned wrong value", + format.isParseIntegerOnly()); + + // try with a locale that has a different integer pattern + // BEGIN android-changed + // use de_CH instead + // format = (DecimalFormat) NumberFormat.getIntegerInstance(arLocale); + format = (DecimalFormat) NumberFormat.getIntegerInstance(chLocale); + assertEquals( + "Test7: NumberFormat.getIntegerInstance(new Locale(\"de\", \"CH\")).toPattern() returned wrong pattern", + "#,##0", format.toPattern()); + assertEquals( + "Test8: NumberFormat.getIntegerInstance(new Locale(\"de\", \"CH\")).format(-35.76) returned wrong value", + "-36", format.format(-35.76)); + assertEquals( + "Test9: NumberFormat.getIntegerInstance(new Locale(\"de\", \"CH\")).parse(\"-36-\") returned wrong number", + new Long(-36), format.parse("-36")); + assertEquals( + "Test10: NumberFormat.getIntegerInstance(new Locale(\"de\", \"CH\")).parseObject(\"36-\") returned wrong number", + new Long(-36), format.parseObject("-36")); + + assertEquals( + "Test11: NumberFormat.getIntegerInstance(new Locale(\"de\", \"CH\")).getMaximumFractionDigits() returned wrong value", + 0, format.getMaximumFractionDigits()); + assertTrue( + "Test12: NumberFormat.getIntegerInstance(new Locale(\"de\", \"CH\")).isParseIntegerOnly() returned wrong value", + format.isParseIntegerOnly()); + // use de_CH instead + /*assertEquals( + "Test7: NumberFormat.getIntegerInstance(new Locale(\"ar\", \"AE\")).toPattern() returned wrong pattern", + "#,##0;#,##0-", format.toPattern()); + assertEquals( + "Test8: NumberFormat.getIntegerInstance(new Locale(\"ar\", \"AE\")).format(-35.76) returned wrong value", + "36-", format.format(-35.76)); + assertEquals( + "Test9: NumberFormat.getIntegerInstance(new Locale(\"ar\", \"AE\")).parse(\"-36-\") returned wrong number", + new Long(-36), format.parse("36-")); + assertEquals( + "Test10: NumberFormat.getIntegerInstance(new Locale(\"ar\", \"AE\")).parseObject(\"36-\") returned wrong number", + new Long(-36), format.parseObject("36-")); + + assertEquals( + "Test11: NumberFormat.getIntegerInstance(new Locale(\"ar\", \"AE\")).getMaximumFractionDigits() returned wrong value", + 0, format.getMaximumFractionDigits()); + assertTrue( + "Test12: NumberFormat.getIntegerInstance(new Locale(\"ar\", \"AE\")).isParseIntegerOnly() returned wrong value", + format.isParseIntegerOnly());*/ + // END android-changed + } + + /** + * @tests java.text.NumberFormat#getCurrency() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCurrency", + args = {} + ) + public void test_getCurrency() { + // Test for method java.util.Currency getCurrency() + + // a subclass that supports currency formatting + Currency currH = Currency.getInstance("HUF"); + NumberFormat format = NumberFormat.getInstance(new Locale("hu", "HU")); + assertSame("Returned incorrect currency", currH, format.getCurrency()); + + // a subclass that doesn't support currency formatting + ChoiceFormat cformat = new ChoiceFormat( + "0#Less than one|1#one|1<Between one and two|2<Greater than two"); + try { + ((NumberFormat) cformat).getCurrency(); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + } + } + + /** + * @tests java.text.NumberFormat#setMaximumIntegerDigits() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMaximumIntegerDigits", + args = {int.class} + ) + public void test_setMaximumIntegerDigits() { + NumberFormat format = NumberFormat.getInstance(); + format.setMaximumIntegerDigits(2); + assertEquals("Wrong result: case 1", "23", format.format(123)); + + format.setMaximumIntegerDigits(Integer.MIN_VALUE); + assertEquals("Wrong result: case 2", "0", format.format(123)); + } + + /** + * @tests java.text.NumberFormat#setCurrency(java.util.Currency) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setCurrency", + args = {java.util.Currency.class} + ) + public void test_setCurrencyLjava_util_Currency() { + // Test for method void setCurrency(java.util.Currency) + // a subclass that supports currency formatting + Currency currA = Currency.getInstance("ARS"); + NumberFormat format = NumberFormat.getInstance(new Locale("hu", "HU")); + format.setCurrency(currA); + assertSame("Returned incorrect currency", currA, format.getCurrency()); + + // a subclass that doesn't support currency formatting + ChoiceFormat cformat = new ChoiceFormat( + "0#Less than one|1#one|1<Between one and two|2<Greater than two"); + try { + ((NumberFormat) cformat).setCurrency(currA); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + } + + try { + NumberFormat.getInstance().setCurrency(null); + fail("NullPointerException was thrown."); + } catch(NullPointerException npe) { + //expected + } + + try { + NumberFormat.getIntegerInstance().setCurrency(null); + fail("NullPointerException was thrown."); + } catch(NullPointerException npe) { + //expected + } + } + + /** + * @tests java.text.NumberFormat#parseObject(java.lang.String, + * java.text.ParsePosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "parseObject", + args = {java.lang.String.class, java.text.ParsePosition.class} + ) + public void test_parseObjectLjava_lang_StringLjava_text_ParsePosition() { + // regression test for HARMONY-1003 + assertNull(NumberFormat.getInstance().parseObject("0", + new ParsePosition(-1))); + + parseObjectTest(NumberFormat.getInstance(), "123.123", + new ParsePosition(1), new Double(23.123), 7, true); + + parseObjectTest(NumberFormat.getInstance(), "123.123abc123", + new ParsePosition(3), new Double(0.123), 7, true); + + parseObjectTest(NumberFormat.getInstance(Locale.FRANCE), + "asd123,123abc123", + new ParsePosition(3), new Double(123.123), 10, true); + + parseObjectTest(NumberFormat.getInstance(Locale.FRANCE), + "test test", + new ParsePosition(0), null, 0, false); + + parseObjectTest(NumberFormat.getIntegerInstance(), + "asd123.123abc123", + new ParsePosition(3), new Long(123), 6, true); + + parseObjectTest(NumberFormat.getNumberInstance(), + "$-123,123.123#", + new ParsePosition(1), new Double(-123123.123), 13, true); + parseObjectTest(NumberFormat.getNumberInstance(), + "$-123,123.123#", + new ParsePosition(0), null, 0, false); + parseObjectTest(NumberFormat.getNumberInstance(), + "$-123,123.123#", + new ParsePosition(13), null, 13, false); + parseObjectTest(NumberFormat.getPercentInstance(), + "%20.123#", + new ParsePosition(0), new Double(20.123), 0, false); + parseObjectTest(NumberFormat.getPercentInstance(), + "%-200,123.123#", + new ParsePosition(0), null, 0, false); + + + // Regression for HARMONY-1685 + try { + NumberFormat.getInstance().parseObject("test", null); + fail("NullPointerException expected"); + } catch (NullPointerException e) { + // expected + } + } + + void parseObjectTest(NumberFormat nf, String sourseStr, ParsePosition position, + Object resultObj, int outIndex, boolean isSuccess) { + int indexBefore = position.getIndex(); + Object result = nf.parseObject(sourseStr, position); + if(isSuccess) { + assertEquals(resultObj, result); + assertEquals(outIndex, position.getIndex()); + } else { + assertNull(result); + assertEquals(indexBefore, position.getIndex()); + assertEquals(outIndex, position.getErrorIndex()); + } + } + + /** + * @tests java.text.NumberFormat#clone() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + + int max_digits = 100; + NumberFormat nf1 = NumberFormat.getInstance(); + nf1.setMaximumIntegerDigits(max_digits); + + NumberFormat nf2 = (NumberFormat) nf1.clone(); + NumberFormat nf3 = (NumberFormat) nf1.clone(); + + assertTrue("Clonned object is not equal to object", nf2.equals(nf1)); + assertTrue("Two clonned objects are not equal", nf2.equals(nf3)); + + assertTrue("Max digits value is incorrect for clonned object", nf2 + .getMaximumIntegerDigits() == max_digits); + + nf1.setMaximumIntegerDigits(10); + assertTrue( + "Max digits value is incorrect for clonned object after changing this value for object", + nf2.getMaximumIntegerDigits() == max_digits); + } + + /** + * @tests java.text.NumberFormat#equals(Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equals() { + + NumberFormat nf1 = NumberFormat.getInstance(); + NumberFormat nf2 = NumberFormat.getInstance(); + + assertTrue("Objects are not equal", nf1.equals(nf2)); + assertTrue("THe same Objects are not equal", nf1.equals(nf1)); + + nf2.setMaximumIntegerDigits(100); + assertFalse("Different NumberFormat are equal", nf1.equals(nf2)); + + nf2.setMaximumIntegerDigits(nf1.getMaximumIntegerDigits()); + assertTrue("THe same Objects are not equal", nf1.equals(nf2)); + + nf1 = NumberFormat.getIntegerInstance(); + nf2 = NumberFormat.getIntegerInstance(Locale.CHINA); + assertFalse("Different NumberFormat are equal", nf1.equals(nf2)); + + assertFalse("Object is equal null", nf1.equals(null)); + } + + /** + * @tests java.text.NumberFormat#format(double) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {double.class} + ) + public void test_formatLdouble() { + // BEGIN android-changed + NumberFormat nf1 = NumberFormat.getInstance(Locale.US); + // use de_CH instead + // NumberFormat nf2 = NumberFormat.getInstance(new Locale("ar", "AR")); + NumberFormat nf2 = NumberFormat.getInstance(new Locale("de", "CH")); + + String out = nf1.format(1234567890.0123456789); + assertEquals("Wrong result for double : " + out, "1,234,567,890.012", + out.toString()); + + out = nf1.format(-1234567890.0123456789); + assertEquals("Wrong result for double : " + out, "-1,234,567,890.012", + out.toString()); + + out = nf2.format(-1234567890.0123456789); + // use de_CH instead + // assertEquals("Wrong result for double : " + out, "1,234,567,890.012-", + // out.toString()); + assertEquals("Wrong result for double : " + out, "-1'234'567'890.012", + out.toString()); + + out = nf1.format(1.0001); + assertEquals("Wrong result for for double: " + out, "1", out.toString()); + + out = nf1.format(5.0); + assertEquals("Wrong result for for double: " + out, "5", out.toString()); + // END android-changed + } + + /** + * @tests java.text.NumberFormat#format(long) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "format", + args = {long.class} + ) + public void test_formatLlong() { + // BEGIN android-changed + NumberFormat nf1 = NumberFormat.getInstance(Locale.US); + // use de_CH instead + // NumberFormat nf2 = NumberFormat.getInstance(Locale.CANADA_FRENCH); + NumberFormat nf2 = NumberFormat.getInstance(new Locale("de", "CH")); + + String out = nf1.format(Long.MAX_VALUE); + assertEquals("Wrong result for double : " + out, + "9,223,372,036,854,775,807", out.toString()); + + out = nf1.format(Long.MIN_VALUE); + assertEquals("Wrong result for double : " + out, + "-9,223,372,036,854,775,808", out.toString()); + + out = nf2.format(-1234567890); + // use de_CH instead + // assertEquals("Wrong result for double : " + out, "-1 234 567 890", out + // .toString()); + assertEquals("Wrong result for double : " + out, "-1'234'567'890", out + .toString()); + + // the Locale data of icu uses \uc2a0 + out = nf1.format(1); + assertEquals("Wrong result for for double: " + out, "1", out.toString()); + + out = nf1.format(0); + assertEquals("Wrong result for for double: " + out, "0", out.toString()); + // END android-changed + } + + /** + * @tests java.text.NumberFormat#getAvailableLocales() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getAvailableLocales", + args = {} + ) + public void test_getAvailableLocales() { + + Locale[] l = NumberFormat.getAvailableLocales(); + assertFalse("returned Locale array is null", l == null); + assertTrue("returned Locale length <= 0", l.length > 0); + Locale[] resl = Locale.getAvailableLocales(); + assertTrue("returned Locale arrays are different", + l.length == resl.length); + boolean isUS = false; + for (int i = 0; i < resl.length; i++) { + assertEquals("elements " + i + " are not equal: ", resl[i], l[i]); + if (l[i].equals(Locale.US)) + isUS = true; + } + assertTrue("there is no Locale.US", isUS); + } + + /** + * @tests java.text.NumberFormat#getCurrencyInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCurrencyInstance", + args = {} + ) + public void test_getCurrencyInstance() { + + NumberFormat format = NumberFormat.getCurrencyInstance(); + + assertNotSame("Instance is null", null, format); + assertTrue("Object is not instance of NumberFormat", + format instanceof NumberFormat); + + assertEquals( + "Test1: NumberFormat.getCurrencyInstance().format(35.76) returned wrong value", + "$35.76", format.format(35.76)); + assertEquals( + "Test2: NumberFormat.getCurrencyInstance().format(123456.789) returned wrong value", + "$123,456.79", format.format(123456.789)); + assertEquals( + "Test3: NumberFormat.getCurrencyInstance().format(0.1) returned wrong value", + "$0.10", format.format(0.1)); + assertEquals( + "Test4: NumberFormat.getCurrencyInstance().format(0.999) returned wrong value", + "$1.00", format.format(0.999)); + } + + /** + * @tests java.text.NumberFormat#getCurrencyInstance(java.util.Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCurrencyInstance", + args = {java.util.Locale.class} + ) + public void test_getCurrencyInstanceLjava_util_Locale() { + // BEGIN android-changed + Locale usLocale = Locale.US; + // use de_AT instead + // Locale mkLocale = new Locale("mk", "MK"); + Locale atLocale = new Locale("de", "AT"); + + NumberFormat format = NumberFormat.getCurrencyInstance(usLocale); + + assertNotSame("Instance is null", null, format); + assertTrue("Object is not instance of NumberFormat", + format instanceof NumberFormat); + + assertEquals( + "Test1: NumberFormat.getCurrencyInstance(Locale.US).format(35.76) returned wrong value", + "$35.76", format.format(35.76)); + assertEquals( + "Test2: NumberFormat.getCurrencyInstance(Locale.US).format(123456.789) returned wrong value", + "$123,456.79", format.format(123456.789)); + assertEquals( + "Test3: NumberFormat.getCurrencyInstance(Locale.US).format(0.1) returned wrong value", + "$0.10", format.format(0.1)); + assertEquals( + "Test4: NumberFormat.getCurrencyInstance(Locale.US).format(0.999) returned wrong value", + "$1.00", format.format(0.999)); + + // use de_AT instead + // format = NumberFormat.getCurrencyInstance(mkLocale); + format = NumberFormat.getCurrencyInstance(atLocale); + + assertEquals( + "Test5: NumberFormat.getCurrencyInstance(new Locale(\"de\", \"AT\")).format(35.76) returned wrong value", + "\u20ac 35,76", format.format(35.76)); + assertEquals( + "Test6: NumberFormat.getCurrencyInstance(new Locale(\"de\", \"AT\")).format(123456.789) returned wrong value", + "\u20ac 123.456,79", format.format(123456.789)); + assertEquals( + "Test7: NumberFormat.getCurrencyInstance(new Locale(\"de\", \"AT\")).format(0.1) returned wrong value", + "\u20ac 0,10", format.format(0.1)); + assertEquals( + "Test8: NumberFormat.getCurrencyInstance(new Locale(\"de\", \"AT\")).format(0.999) returned wrong value", + "\u20ac 1,00", format.format(0.999)); + // use de_AT instead + /*assertEquals( + "Test5: NumberFormat.getCurrencyInstance(new Locale(\"mk\", \"MK\")).format(35.76) returned wrong value", + "Den 35,76", format.format(35.76)); + assertEquals( + "Test6: NumberFormat.getCurrencyInstance(new Locale(\"mk\", \"MK\")).format(123456.789) returned wrong value", + "Den 123.456,79", format.format(123456.789)); + assertEquals( + "Test7: NumberFormat.getCurrencyInstance(new Locale(\"mk\", \"MK\")).format(0.1) returned wrong value", + "Den 0,1", format.format(0.1)); + assertEquals( + "Test8: NumberFormat.getCurrencyInstance(new Locale(\"mk\", \"MK\")).format(0.999) returned wrong value", + "Den 1", format.format(0.999));*/ + try { + NumberFormat.getCurrencyInstance(null); + fail("java.lang.NullPointerException is not thrown"); + } catch (java.lang.NullPointerException npe) { + // expested + } + // END android-changed + } + + /** + * @tests java.text.NumberFormat#getInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getInstance", + args = {} + ) + public void test_getInstance() { + Locale.setDefault(Locale.US); + NumberFormat format = NumberFormat.getInstance(); + + assertNotSame("Instance is null", null, format); + assertTrue("Object is not instance of NumberFormat", + format instanceof NumberFormat); + + assertEquals( + "Test1: NumberFormat.getInstance().format(1234567890.0987654321) returned wrong value", + "1,234,567,890.099", format.format(1234567890.0987654321)); + assertEquals( + "Test2: ((DecimalFormat) NumberFormat.getInstance()).toPattern returned wrong value", + "#,##0.###", ((DecimalFormat) format).toPattern()); + assertEquals( + "Test3: NumberFormat.getInstance().format(123456789) returned wrong value", + "123,456,789", format.format(123456789)); + } + + /** + * @tests java.text.NumberFormat#getInstance(Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getInstance", + args = {java.util.Locale.class} + ) + public void test_getInstanceLjava_util_Locale() { + // BEGIN android-changed + Locale.setDefault(Locale.US); + // use de_CH instead + // NumberFormat format = NumberFormat.getInstance(new Locale("ar", "AR")); + NumberFormat format = NumberFormat.getInstance(new Locale("de", "CH")); + + assertNotSame("Instance is null", null, format); + assertTrue("Object is not instance of NumberFormat", + format instanceof NumberFormat); + + assertEquals( + "Test1: NumberFormat.getInstance().format(1234567890.0987654321) returned wrong value", + "1'234'567'890.099", format.format(1234567890.0987654321)); + assertEquals( + "Test2: ((DecimalFormat) NumberFormat.getInstance()).toPattern returned wrong value", + "#,##0.###", ((DecimalFormat) format).toPattern()); + assertEquals( + "Test3: NumberFormat.getInstance().format(123456789) returned wrong value", + "123'456'789", format.format(123456789)); + // use de_CH instead + /*assertEquals( + "Test1: NumberFormat.getInstance().format(1234567890.0987654321) returned wrong value", + "1,234,567,890.099", format.format(1234567890.0987654321)); + assertEquals( + "Test2: ((DecimalFormat) NumberFormat.getInstance()).toPattern returned wrong value", + "#,##0.###;#,##0.###-", ((DecimalFormat) format).toPattern()); + assertEquals( + "Test3: NumberFormat.getInstance().format(123456789) returned wrong value", + "123,456,789", format.format(123456789));*/ + try { + NumberFormat.getInstance(null); + fail("java.lang.NullPointerException is not thrown"); + } catch (java.lang.NullPointerException npe) { + // expested + } + // END android-changed + } + + /** + * @tests java.text.NumberFormat#getNumberInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getNumberInstance", + args = {} + ) + public void test_getNumberInstance() { + Locale.setDefault(Locale.US); + NumberFormat format = NumberFormat.getNumberInstance(); + + assertNotSame("Instance is null", null, format); + assertTrue("Object is not instance of NumberFormat", + format instanceof NumberFormat); + + assertEquals( + "Test1: NumberFormat.getNumberInstance().format(1234567890.0987654321) returned wrong value", + "1,234,567,890.099", format.format(1234567890.0987654321)); + assertEquals( + "Test2: ((DecimalFormat) NumberFormat.getNumberInstance()).toPattern returned wrong value", + "#,##0.###", ((DecimalFormat) format).toPattern()); + assertEquals( + "Test3: NumberFormat.getNumberInstance().format(123456789) returned wrong value", + "123,456,789", format.format(123456789)); + } + + /** + * @tests java.text.NumberFormat#getNumberInstance(Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getNumberInstance", + args = {java.util.Locale.class} + ) + public void test_getNumberInstanceLjava_util_Locale() { + // BEGIN android-changed + Locale.setDefault(Locale.US); + // use de_CH instead + NumberFormat format = NumberFormat.getNumberInstance(new Locale("de", + "CH")); + // NumberFormat format = NumberFormat.getNumberInstance(new Locale("ar", + // "AR")); + + assertNotSame("Instance is null", null, format); + assertTrue("Object is not instance of NumberFormat", + format instanceof NumberFormat); + + assertEquals( + "Test1: NumberFormat.getNumberInstance().format(-1234567890.0987654321) returned wrong value", + "-1'234'567'890.099", format.format(-1234567890.0987654321)); + assertEquals( + "Test2: ((DecimalFormat) NumberFormat.getNumberInstance()).toPattern returned wrong value", + "#,##0.###", ((DecimalFormat) format).toPattern()); + assertEquals( + "Test3: NumberFormat.getNumberInstance().format(123456789) returned wrong value", + "123'456'789", format.format(123456789)); + // use de_CH instead + /*assertEquals( + "Test1: NumberFormat.getNumberInstance().format(-1234567890.0987654321) returned wrong value", + "1,234,567,890.099-", format.format(-1234567890.0987654321)); + assertEquals( + "Test2: ((DecimalFormat) NumberFormat.getNumberInstance()).toPattern returned wrong value", + "#,##0.###;#,##0.###-", ((DecimalFormat) format).toPattern()); + assertEquals( + "Test3: NumberFormat.getNumberInstance().format(123456789) returned wrong value", + "123,456,789", format.format(123456789));*/ + try { + NumberFormat.getInstance(null); + fail("java.lang.NullPointerException is not thrown"); + } catch (java.lang.NullPointerException npe) { + // expested + } + // END android-changed + } + + /** + * @tests java.text.NumberFormat#getPercentInstance() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getPercentInstance", + args = {} + ) + public void test_getPercentInstance() { + Locale.setDefault(Locale.US); + NumberFormat format = NumberFormat.getPercentInstance(); + + assertNotSame("Instance is null", null, format); + assertTrue("Object is not instance of NumberFormat", + format instanceof NumberFormat); + + assertEquals( + "Test1: NumberFormat.getPercentInstance().format(1234567890.0987654321) returned wrong value", + "123,456,789,010%", format.format(1234567890.0987654321)); + assertEquals( + "Test2: ((DecimalFormat) NumberFormat.getPercentInstance()).toPattern returned wrong value", + "#,##0%", ((DecimalFormat) format).toPattern()); + assertEquals( + "Test3: NumberFormat.getPercentInstance().format(123456789) returned wrong value", + "12,345,678,900%", format.format(123456789)); + } + + /** + * @tests java.text.NumberFormat#getPercentInstance(Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getPercentInstance", + args = {java.util.Locale.class} + ) + public void test_getPercentInstanceLjava_util_Locale() { + Locale.setDefault(Locale.US); + NumberFormat format = NumberFormat.getPercentInstance(new Locale("cs", + "CZ")); + + assertNotSame("Instance is null", null, format); + assertTrue("Object is not instance of NumberFormat", + format instanceof NumberFormat); + + assertEquals( + "Test1: NumberFormat.getPercentInstance().format(1234567890.0987654321) returned wrong value", + "123\u00a0456\u00a0789\u00a0010%", format.format(1234567890.0987654321)); + assertEquals( + "Test2: ((DecimalFormat) NumberFormat.getPercentInstance()).toPattern returned wrong value", + "#,##0%", ((DecimalFormat) format).toPattern()); + assertEquals( + "Test3: NumberFormat.getPercentInstance().format(123456789) returned wrong value", + "12\u00a0345\u00a0678\u00a0900%", format.format(123456789)); + try { + NumberFormat.getInstance(null); + fail("java.lang.NullPointerException is not thrown"); + } catch (java.lang.NullPointerException npe) { + // expested + } + } + + /** + * @tests java.text.NumberFormat#getMaximumFractionDigits() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMaximumFractionDigits", + args = {} + ) + public void test_getMaximumFractionDigits() { + NumberFormat nf1 = NumberFormat.getInstance(); + + nf1.setMaximumFractionDigits(Integer.MAX_VALUE); + int result = nf1.getMaximumFractionDigits(); + assertTrue("getMaximumFractionDigits returns " + result + + " instead of: " + Integer.MAX_VALUE, + result == Integer.MAX_VALUE); + + nf1.setMaximumFractionDigits(0); + result = nf1.getMaximumFractionDigits(); + assertTrue("getMaximumFractionDigits returns " + result + + " instead of 0", result == 0); + + nf1.setMinimumFractionDigits(Integer.MAX_VALUE); + result = nf1.getMaximumFractionDigits(); + assertTrue("getMaximumFractionDigits returns " + result + + " instead of Integer.MAX_VALUE", result == Integer.MAX_VALUE); + } + + /** + * @tests java.text.NumberFormat#getMinimumFractionDigits() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMinimumFractionDigits", + args = {} + ) + public void test_getMinimumFractionDigits() { + NumberFormat nf1 = NumberFormat.getInstance(); + nf1.setMinimumFractionDigits(Integer.MAX_VALUE); + int result = nf1.getMinimumFractionDigits(); + assertTrue("getMinimumFractionDigits returns " + result + + " instead of: " + Integer.MAX_VALUE, + result == Integer.MAX_VALUE); + + nf1.setMaximumFractionDigits(0); + result = nf1.getMinimumFractionDigits(); + assertTrue("getMinimumFractionDigits returns " + result + + " instead of 0", result == 0); + + nf1.setMinimumFractionDigits(52); + result = nf1.getMinimumFractionDigits(); + assertTrue("getMinimumFractionDigits returns " + result + + " instead of 52", result == 52); + } + + /** + * @tests java.text.NumberFormat#getMaximumIntegerDigits() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMaximumIntegerDigits", + args = {} + ) + public void test_getMaximumIntegerDigits() { + NumberFormat nf1 = NumberFormat.getInstance(); + nf1.setMaximumIntegerDigits(Integer.MAX_VALUE); + int result = nf1.getMaximumIntegerDigits(); + assertTrue("getMaximumIntegerDigits returns " + result + + " instead of: " + Integer.MAX_VALUE, + result == Integer.MAX_VALUE); + + nf1.setMaximumIntegerDigits(0); + result = nf1.getMaximumIntegerDigits(); + assertTrue("getMaximumIntegerDigits returns " + result + + " instead of 0", result == 0); + + nf1.setMinimumIntegerDigits(Integer.MAX_VALUE); + result = nf1.getMaximumIntegerDigits(); + assertTrue("getMaximumIntegerigits returns " + result + + " instead of Integer.MAX_VALUE", result == Integer.MAX_VALUE); + } + + /** + * @tests java.text.NumberFormat#getMinimumIntegerDigits() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMinimumIntegerDigits", + args = {} + ) + public void test_getMinimumIntegernDigits() { + NumberFormat nf1 = NumberFormat.getInstance(); + nf1.setMinimumIntegerDigits(Integer.MAX_VALUE); + int result = nf1.getMinimumIntegerDigits(); + assertTrue("getMinimumIntegerDigits returns " + result + + " instead of: " + Integer.MAX_VALUE, + result == Integer.MAX_VALUE); + + nf1.setMaximumIntegerDigits(0); + result = nf1.getMinimumIntegerDigits(); + assertTrue("getMinimumIntegerDigits returns " + result + + " instead of 0", result == 0); + + nf1.setMinimumIntegerDigits(0x12034); + result = nf1.getMinimumIntegerDigits(); + assertTrue("getMinimumIntegerDigits returns " + result + + " instead of 5148", result == 73780); + } + + /** + * @tests java.text.NumberFormat#hashCode() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + + NumberFormat nf1 = NumberFormat.getInstance(); + NumberFormat nf11 = NumberFormat.getInstance(); + NumberFormat nf2 = NumberFormat.getInstance(Locale.US); + NumberFormat nf3 = NumberFormat.getPercentInstance(); + NumberFormat nf4 = NumberFormat.getCurrencyInstance(); + NumberFormat nf5 = NumberFormat + .getNumberInstance(new Locale("mk", "MK")); + NumberFormat nf6 = NumberFormat.getInstance(Locale.US); + + assertTrue("Hash codes are not equal: case 1", nf1.hashCode() == nf2 + .hashCode()); + assertTrue("Hash codes are not equal: case 2", nf1.hashCode() == nf11 + .hashCode()); + assertTrue("Hash codes are not equal: case 3", nf1.hashCode() == nf3 + .hashCode()); + assertFalse("Hash codes are equal: case 4", nf3.hashCode() == nf4 + .hashCode()); + assertFalse("Hash codes are equal: case 5", nf4.hashCode() == nf5 + .hashCode()); + assertTrue("Hash codes are not equal: case 6", nf5.hashCode() == nf6 + .hashCode()); + + nf1.setMaximumFractionDigits(0); + assertTrue("Hash codes are not equal: case 7", nf1.hashCode() == nf11 + .hashCode()); + } + + /** + * @tests java.text.NumberFormat#isGroupingUsed() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "isGroupingUsed", + args = {} + ) + public void test_isGroupingUsed() { + NumberFormat nf1 = NumberFormat.getInstance(); + assertTrue("grouping is not used for NumberFormat.getInstance", nf1 + .isGroupingUsed()); + + nf1.setGroupingUsed(false); + assertFalse( + "grouping is used for NumberFormat.getInstance after setting false", + nf1.isGroupingUsed()); + + nf1.setGroupingUsed(true); + assertTrue( + "grouping is not used for NumberFormat.getInstance after setting true", + nf1.isGroupingUsed()); + } + + /** + * @tests java.text.NumberFormat#setGroupingUsed(boolean) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setGroupingUsed", + args = {boolean.class} + ) + public void test_setGroupingUsed() { + NumberFormat nf1 = NumberFormat.getInstance(Locale.US); + nf1.setGroupingUsed(false); + + assertEquals("grouping is used for 1234567890.1", "1234567890.1", + nf1.format(1234567890.1)); + + assertEquals("grouping is used for -1234567890.1", "-1234567890.1", + nf1.format(-1234567890.1)); + + nf1.setGroupingUsed(false); + + assertEquals("grouping is used for 1234567890.1", "1234567890.1", + nf1.format(1234567890.1)); + + assertEquals("grouping is used for -1234567890.1", "-1234567890.1", + nf1.format(-1234567890.1)); + + nf1.setGroupingUsed(true); + + assertEquals("grouping is not used for 1234567890.1", + "1,234,567,890.1", nf1.format(1234567890.1)); + + assertEquals("grouping is not used for -1234567890.1", + "-1,234,567,890.1", nf1.format(-1234567890.1)); + + NumberFormat nf2 = NumberFormat.getPercentInstance(new Locale("cs", + "CZ")); + nf2.setGroupingUsed(false); + assertEquals( + "Locale(\"cs\", \"CZ\"): grouping is used for 1234567890.1", + "123456789010%", nf2.format(1234567890.1)); + + assertEquals( + "Locale(\"cs\", \"CZ\"): grouping is used for -1234567890.1", + "-123456789010%", nf2.format(-1234567890.1)); + assertEquals("grouping is not used for 1234567890.1", + "1,234,567,890.1", nf1.format(1234567890.1)); + + nf2.setGroupingUsed(true); + assertEquals( + "Locale(\"cs\", \"CZ\"): grouping is not used for 1234567890.1", + "123\u00a0456\u00a0789\u00a0010%", nf2.format(1234567890.1)); + + assertEquals( + "Locale(\"cs\", \"CZ\"): grouping is not used for -1234567890.1", + "-123\u00a0456\u00a0789\u00a0010%", nf2.format(-1234567890.1)); + + nf2.setGroupingUsed(true); + assertEquals( + "Locale(\"cs\", \"CZ\"): grouping is not used for 1234567890.1", + "123\u00a0456\u00a0789\u00a0010%", nf2.format(1234567890.1)); + + assertEquals( + "Locale(\"cs\", \"CZ\"): grouping is not used for -1234567890.1", + "-123\u00a0456\u00a0789\u00a0010%", nf2.format(-1234567890.1)); + } + + /** + * @tests java.text.NumberFormat#isParseIntegerOnly() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "isParseIntegerOnly", + args = {} + ) + public void test_isParseIntegerOnly() { + NumberFormat nf1 = NumberFormat.getInstance(); + assertTrue("ParseIntegerOnly is not used for NumberFormat.getInstance", + nf1.isGroupingUsed()); + + nf1.setParseIntegerOnly(false); + assertFalse( + "ParseIntegerOnly is used for NumberFormat.getInstance after setting false", + nf1.isParseIntegerOnly()); + + nf1.setParseIntegerOnly(true); + assertTrue( + "ParseIntegerOnly is not used for NumberFormat.getInstance after setting true", + nf1.isParseIntegerOnly()); + } + + /** + * @tests java.text.NumberFormat#setParseIntegerOnly(boolean) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setParseIntegerOnly", + args = {boolean.class} + ) + public void test_setParseIntegerOnly() { + NumberFormat nf1 = NumberFormat.getInstance(Locale.US); + nf1.setParseIntegerOnly(true); + + assertEquals("ParseIntegerOnly is not used for 1234567890.1", + "1,234,567,890.1", nf1.format(1234567890.1)); + assertEquals("ParseIntegerOnly is not used for -1234567890.1", + "-1,234,567,890.1", nf1.format(-1234567890.1)); + assertEquals("ParseIntegerOnly is not used for -1234567890.", + "-1,234,567,890", nf1.format(-1234567890.)); + + nf1.setParseIntegerOnly(false); + + assertEquals("ParseIntegerOnly is not used for 1234567890.1", + "1,234,567,890.1", nf1.format(1234567890.1)); + assertEquals("ParseIntegerOnly is not used for -1234567890.1", + "-1,234,567,890.1", nf1.format(-1234567890.1)); + assertEquals("ParseIntegerOnly is not used for -1234567890.", + "-1,234,567,890", nf1.format(-1234567890.)); + } + + /** + * @tests java.text.NumberFormat#setMaximumFractionDigits(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMaximumFractionDigits", + args = {int.class} + ) + public void test_setMaximumFractionDigits() { + NumberFormat nf1 = NumberFormat.getInstance(Locale.US); + nf1.setMaximumFractionDigits(Integer.MAX_VALUE); + int result = nf1.getMaximumFractionDigits(); + assertTrue("setMaximumFractionDigits set " + result + + " instead of Integer.MAX_VALUE", result == Integer.MAX_VALUE); + nf1.setMaximumFractionDigits(0); + result = nf1.getMaximumFractionDigits(); + assertTrue("setMaximumFractionDigits set " + result + " instead of 0", + result == 0); + assertEquals("format of 1234567890.0987654321 returns incorrect value", + "1,234,567,890", nf1.format(1234567890.0987654321)); + nf1.setMaximumFractionDigits(5); + result = nf1.getMaximumFractionDigits(); + assertTrue("setMaximumFractionDigits set " + result + " instead of 5", + result == 5); + assertEquals( + "format of 1234567890.0987654321 returns incorrect value with MaximumFractionDigits = 5", + "1,234,567,890.09877", nf1.format(1234567890.0987654321)); + assertEquals( + "format of -1234567890 returns incorrect value with MaximumFractionDigits = 5", + "-1,234,567,890", nf1.format(-1234567890)); + nf1.setMaximumFractionDigits(Integer.MIN_VALUE); + result = nf1.getMaximumFractionDigits(); + assertTrue("setMaximumFractionDigits set " + result + + " instead of Integer.MIN_VALUE", result == 0); + assertEquals( + "format of 1234567890.0987654321 returns incorrect value with MaximumFractionDigits = 5", + "1,234,567,890", nf1.format(1234567890.0987654321)); + } + + /** + * @tests java.text.NumberFormat#setMinimumFractionDigits(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMinimumFractionDigits", + args = {int.class} + ) + public void test_setMinimumFractionDigits() { + + NumberFormat nf1 = NumberFormat.getInstance(Locale.US); + nf1.setMinimumFractionDigits(Integer.MAX_VALUE); + int result = nf1.getMinimumFractionDigits(); + assertTrue("setMinimumFractionDigits set " + result + + " instead of Integer.MAX_VALUE", result == Integer.MAX_VALUE); + nf1.setMinimumFractionDigits(0); + result = nf1.getMinimumFractionDigits(); + assertTrue("setMinimumFractionDigits set " + result + " instead of 0", + result == 0); + nf1.setMinimumFractionDigits(5); + result = nf1.getMinimumFractionDigits(); + assertTrue("setMinimumFractionDigits set " + result + " instead of 5", + result == 5); + assertEquals( + "format of 1234567890.0987654321 returns incorrect value with MinimumFractionDigits = 5", + "1,234,567,890.09000", nf1.format(1234567890.09)); + assertEquals( + "format of -1234567890 returns incorrect value with MinimumFractionDigits = 5", + "-1,234,567,890.00000", nf1.format(-1234567890)); + nf1.setMinimumFractionDigits(Integer.MIN_VALUE); + result = nf1.getMinimumFractionDigits(); + assertTrue("setMinimumFractionDigits set " + result + + " instead of Integer.MIN_VALUE", result == 0); + assertEquals( + "format of 1234567890.098 returns incorrect value with MinimumFractionDigits = 5", + "1,234,567,890.098", nf1.format(1234567890.098)); + } + + /** + * @tests java.text.NumberFormat#setMinimumIntegerDigits(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMinimumIntegerDigits", + args = {int.class} + ) + public void test_setMinimumIntegerDigits() { + + NumberFormat nf1 = NumberFormat.getInstance(Locale.US); + nf1.setMinimumIntegerDigits(Integer.MAX_VALUE); + int result = nf1.getMinimumIntegerDigits(); + assertTrue("setMinimumIntegerDigits set " + result + + " instead of Integer.MAX_VALUE", result == Integer.MAX_VALUE); + nf1.setMinimumIntegerDigits(0); + result = nf1.getMinimumIntegerDigits(); + assertTrue("setMinimumIntegerDigits set " + result + " instead of 0", + result == 0); + nf1.setMinimumIntegerDigits(5); + result = nf1.getMinimumIntegerDigits(); + assertTrue("setMinimumIntegerDigits set " + result + " instead of 5", + result == 5); + assertEquals( + "format of 123.09 returns incorrect value with MinimumIntegerDigits = 5", + "00,123.09", nf1.format(123.09)); + assertEquals( + "format of -123 returns incorrect value with MinimumIntegerDigits = 5", + "-00,123", nf1.format(-123)); + nf1.setMinimumIntegerDigits(Integer.MIN_VALUE); + result = nf1.getMinimumIntegerDigits(); + assertTrue("setMinimumIntegerDigits set " + result + + " instead of Integer.MIN_VALUE", result == 0); + } + + /** + * @tests java.text.NumberFormat#parse(String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "parse", + args = {java.lang.String.class} + ) + public void test_parseLjava_lang_String() { + NumberFormat nf1 = NumberFormat.getInstance(); + try { + assertEquals( + "Test1: NumberFormat.getInstance().parse(\"1234567890.1\") returned wrong number", + new Double(1234567890.1), nf1.parse("1234567890.1")); + } catch (java.text.ParseException pe) { + fail("java.text.ParseException is thrown for 1234567890.1"); + } + + try { + assertEquals( + "Test2: NumberFormat.getInstance().parse(\"-1234567890.1\") returned wrong number", + new Double(-1234567890.1), nf1.parse("-1,234,567,890.1")); + } catch (java.text.ParseException pe) { + fail("java.text.ParseException is thrown for -1,234,567,890.1"); + } + + try { + nf1.parse("@1,234,567,8901"); + fail("java.text.ParseException is not thrown for 1,234,567,890z1"); + } catch (java.text.ParseException pe) { + // expected + } + + nf1 = NumberFormat.getPercentInstance(); + try { + assertEquals( + "Test3: NumberFormat.getPercentInstance().parse(\"-123%\") returned wrong number", + new Double(-1.23), nf1.parse("-123%")); + } catch (java.text.ParseException pe) { + fail("java.text.ParseException is thrown for -123%"); + } + + nf1 = NumberFormat.getCurrencyInstance(); + try { + assertEquals( + "Test4: NumberFormat.getCurrencyInstance().parse(\"$123\") returned wrong number", + new Long(123), nf1.parse("$123")); + } catch (java.text.ParseException pe) { + fail("java.text.ParseException is thrown for $123"); + } + + try { + assertEquals( + "Test4: NumberFormat.getCurrencyInstance().parse(\"$123abc\") returned wrong number", + new Long(123), nf1.parse("$123abc")); + } catch (java.text.ParseException pe) { + fail("java.text.ParseException is thrown for $123"); + } + + nf1 = NumberFormat.getIntegerInstance(); + try { + assertEquals( + "Test5: NumberFormat.getIntegerInstance().parse(\"-123.123\") returned wrong number", + nf1.parseObject("-123.123"), nf1.parse("-123.123")); + } catch (java.text.ParseException pe) { + fail("java.text.ParseException is thrown for $123"); + } + } + + /** + * @tests java.text.NumberFormat#NumberFormat() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "NumberFormat", + args = {} + ) + public void test_constructor() { + MyNumberFormat mf = new MyNumberFormat(); + assertFalse("Greated NumberFormat object is null", mf == null); + assertTrue( + "Greated NumberFormat object is not instance of NumberFormat", + mf instanceof NumberFormat); + } + + class MyNumberFormat extends NumberFormat { + static final long serialVersionUID = 1L; + + public MyNumberFormat() { + super(); + } + + public StringBuffer format(double number, StringBuffer toAppendTo, + FieldPosition pos) { + + return new StringBuffer(); + } + + public Number parse(String source, ParsePosition parsePosition) { + + return new Double(0); + } + + public StringBuffer format(long number, StringBuffer toAppendTo, + FieldPosition pos) { + return new StringBuffer(); + } + + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/ParseExceptionTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/ParseExceptionTest.java new file mode 100644 index 0000000..1c2da6a --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/ParseExceptionTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import java.text.DateFormat; +import java.text.ParseException; + +@TestTargetClass(ParseException.class) +public class ParseExceptionTest extends junit.framework.TestCase { + + /** + * @tests java.text.ParseException#ParseException(java.lang.String, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "ParseException", + args = {java.lang.String.class, int.class} + ) + public void test_ConstructorLjava_lang_StringI() { + // Test for method java.text.ParseException(java.lang.String, int) + // SM + try { + DateFormat df = DateFormat.getInstance(); + df.parse("HelloWorld"); + } catch (ParseException e) { + return; + } + fail("ParseException not created/thrown."); + } + + /** + * @tests java.text.ParseException#getErrorOffset() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getErrorOffset", + args = {} + ) + public void test_getErrorOffset() { + // Test for method int java.text.ParseException.getErrorOffset() + // SM + try { + DateFormat df = DateFormat.getInstance(); + df.parse("1999HelloWorld"); + } catch (ParseException e) { + assertEquals("getErrorOffsetFailed.", 4, e.getErrorOffset()); + } + } + + /** + * Sets up the fixture, for example, open a network connection. This method + * is called before a test is executed. + */ + protected void setUp() { + } + + /** + * Tears down the fixture, for example, close a network connection. This + * method is called after a test is executed. + */ + protected void tearDown() { + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/ParsePositionTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/ParsePositionTest.java new file mode 100644 index 0000000..9c1fb21 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/ParsePositionTest.java @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import java.text.ParsePosition; + +@TestTargetClass(ParsePosition.class) +public class ParsePositionTest extends junit.framework.TestCase { + + ParsePosition pp; + + /** + * @tests java.text.ParsePosition#ParsePosition(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "ParsePosition", + args = {int.class} + ) + public void test_ConstructorI() { + // Test for method java.text.ParsePosition(int) + try { + ParsePosition pp1 = new ParsePosition(Integer.MIN_VALUE); + assertTrue("Initialization failed.", + pp1.getIndex() == Integer.MIN_VALUE); + assertEquals("Initialization failed.", -1, pp1.getErrorIndex()); + } catch (Exception e) { + fail("Constructor failed."); + } + + } + + /** + * @tests java.text.ParsePosition#equals(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + // Test for method boolean + // java.text.ParsePosition.equals(java.lang.Object) + ParsePosition pp2 = new ParsePosition(43); + pp2.setErrorIndex(56); + assertTrue("equals failed.", !pp.equals(pp2)); + pp.setErrorIndex(56); + pp.setIndex(43); + assertTrue("equals failed.", pp.equals(pp2)); + } + + /** + * @tests java.text.ParsePosition#getErrorIndex() + */ + @TestTargets({ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getErrorIndex", + args = {} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setErrorIndex", + args = {int.class} + ) + }) + public void test_getErrorIndex() { + // Test for method int java.text.ParsePosition.getErrorIndex() + pp.setErrorIndex(56); + assertEquals("getErrorIndex failed.", 56, pp.getErrorIndex()); + pp.setErrorIndex(Integer.MAX_VALUE); + assertEquals("getErrorIndex failed.", Integer.MAX_VALUE, + pp.getErrorIndex()); + assertEquals("getErrorIndex failed.", Integer.MAX_VALUE, + pp.getErrorIndex()); + } + + /** + * @tests java.text.ParsePosition#getIndex() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getIndex", + args = {} + ) + public void test_getIndex() { + // Test for method int java.text.ParsePosition.getIndex() + assertTrue("getIndex failed.", pp.getIndex() == Integer.MAX_VALUE); + } + + /** + * @tests java.text.ParsePosition#hashCode() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + // Test for method int java.text.ParsePosition.hashCode() + ParsePosition pp1 = new ParsePosition(0); + ParsePosition pp2 = new ParsePosition(0); + assertTrue("hashCode returns non equal hash codes for equal objects.", + pp1.hashCode() == pp2.hashCode()); + pp1.setIndex(Integer.MAX_VALUE); + assertTrue("hashCode returns equal hash codes for non equal objects.", + pp1.hashCode() != pp2.hashCode()); + } + + /** + * @tests java.text.ParsePosition#setErrorIndex(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setErrorIndex", + args = {int.class} + ) + public void test_setErrorIndexI() { + // Test for method void java.text.ParsePosition.setErrorIndex(int) + pp.setErrorIndex(4564); + assertEquals("setErrorIndex failed.", 4564, pp.getErrorIndex()); + } + + /** + * @tests java.text.ParsePosition#setIndex(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setIndex", + args = {int.class} + ) + public void test_setIndexI() { + // Test for method void java.text.ParsePosition.setIndex(int) + pp.setIndex(4564); + assertEquals("setErrorIndex failed.", 4564, pp.getIndex()); + } + + /** + * @tests java.text.ParsePosition#toString() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "toString", + args = {} + ) + public void test_toString() { + // Test for method java.lang.String java.text.ParsePosition.toString() + // String format is not specified. + assertNotNull("toString returns null.", pp.toString()); + } + + /** + * Sets up the fixture, for example, open a network connection. This method + * is called before a test is executed. + */ + protected void setUp() { + + pp = new ParsePosition(Integer.MAX_VALUE); + } + + /** + * Tears down the fixture, for example, close a network connection. This + * method is called after a test is executed. + */ + protected void tearDown() { + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/RuleBasedCollatorTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/RuleBasedCollatorTest.java new file mode 100644 index 0000000..7cdd164 --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/RuleBasedCollatorTest.java @@ -0,0 +1,498 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.AndroidOnly; +import dalvik.annotation.BrokenTest; +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargets; + +import junit.framework.TestCase; + +import java.text.CharacterIterator; +import java.text.CollationElementIterator; +import java.text.CollationKey; +import java.text.Collator; +import java.text.ParseException; +import java.text.RuleBasedCollator; +import java.text.StringCharacterIterator; +import java.util.Locale; + +@TestTargetClass(RuleBasedCollator.class) +public class RuleBasedCollatorTest extends TestCase { + + /** + * @tests java.text.RuleBasedCollator#RuleBasedCollator(String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "RuleBasedCollator", + args = {java.lang.String.class} + ) + public void test_constrLRuleBasedCollatorLjava_lang_String() { + RuleBasedCollator rbc; + try { + rbc = new RuleBasedCollator("<a< b< c< d"); + assertNotSame("RuleBasedCollator object is null", null, rbc); + } catch (java.text.ParseException pe) { + fail("java.text.ParseException is thrown for correct string"); + } + + try { + rbc = new RuleBasedCollator("<a< '&'b< \u0301< d"); + assertNotSame("RuleBasedCollator object is null", null, rbc); + } catch (java.text.ParseException pe) { + fail("java.text.ParseException is thrown for correct string"); + } + + try { + new RuleBasedCollator(null); + fail("No Exception is thrown for correct string"); + } catch (java.text.ParseException pe) { + fail("java.lang.NullPointerException is not thrown for correct string"); + } catch (java.lang.NullPointerException npe) { + + } + +// Commented since fails agains RI and Android, too: +// +// // Android allows to pass empty rules to a collator. It results in +// // a collator with default UCA rules. +// try { +// new RuleBasedCollator(""); +// } catch (java.text.ParseException pe) { +// fail("java.text.ParseException is thrown for empty string"); +// } + + try { + new RuleBasedCollator("1234567%$#845"); + fail("java.text.ParseException is not thrown for wrong rules"); + } catch (java.text.ParseException pe) { + } + } + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test. Doesn't verify positive functionality.", + method = "getCollationKey", + args = {java.lang.String.class} + ) + public void test_getCollationKeyLjava_lang_String() { + // Regression test for HARMONY-28 + String source = null; + RuleBasedCollator rbc = null; + try { + String Simple = "< a< b< c< d"; + rbc = new RuleBasedCollator(Simple); + } catch (ParseException e) { + fail("Assert 0: Unexpected format exception " + e); + } + CollationKey ck = rbc.getCollationKey(source); + assertNull("Assert 1: getCollationKey (null) does not return null", ck); + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void testHashCode() throws ParseException { + { + String rule = "< a < b < c < d"; + RuleBasedCollator coll = new RuleBasedCollator(rule); + assertEquals(rule.hashCode(), coll.hashCode()); + } + + { + String rule = "< a < b < c < d < e"; + RuleBasedCollator coll = new RuleBasedCollator(rule); + assertEquals(rule.hashCode(), coll.hashCode()); + } + + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void testClone() throws ParseException { + RuleBasedCollator coll = (RuleBasedCollator) Collator + .getInstance(Locale.US); + RuleBasedCollator clone = (RuleBasedCollator) coll.clone(); + assertNotSame(coll, clone); + assertEquals(coll.getRules(), clone.getRules()); + assertEquals(coll.getDecomposition(), clone.getDecomposition()); + assertEquals(coll.getStrength(), clone.getStrength()); + } + + /* + * Class under test for boolean equals(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "equals", + args = {java.lang.Object.class} + ) + @KnownFailure("Succeeds against RI.") + public void testEqualsObject() throws ParseException { + String rule = "< a < b < c < d < e"; + RuleBasedCollator coll = new RuleBasedCollator(rule); + + assertEquals(Collator.TERTIARY, coll.getStrength()); + assertEquals(Collator.CANONICAL_DECOMPOSITION, coll.getDecomposition()); + RuleBasedCollator other = new RuleBasedCollator(rule); + assertTrue(coll.equals(other)); + + coll.setStrength(Collator.PRIMARY); + assertFalse(coll.equals(other)); + + coll.setStrength(Collator.TERTIARY); + coll.setDecomposition(Collator.CANONICAL_DECOMPOSITION); + assertTrue(coll.equals(other)); + } + + /* + * Class under test for int compare(java.lang.String, java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "compare", + args = {java.lang.String.class, java.lang.String.class} + ) + public void testCompareStringString() throws ParseException { + String rule = "< c < b < a"; + RuleBasedCollator coll = new RuleBasedCollator(rule); + assertEquals(-1, coll.compare("c", "a")); + assertEquals(-1, coll.compare("a", "d")); + assertEquals(1, coll.compare("3", "1")); + assertEquals(1, coll.compare("A", "1")); + assertEquals(0, coll.compare("A", "A")); + } + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Doesn't verify null as a parameter.", + method = "getCollationKey", + args = {java.lang.String.class} + ) + public void testGetCollationKey() { + RuleBasedCollator coll = (RuleBasedCollator) Collator + .getInstance(Locale.GERMAN); + String source = "abc"; + CollationKey key1 = coll.getCollationKey(source); + assertEquals(source, key1.getSourceString()); + String source2 = "abb"; + CollationKey key2 = coll.getCollationKey(source2); + assertEquals(source2, key2.getSourceString()); + assertTrue(key1.compareTo(key2) > 0); + assertTrue(coll.compare(source, source2) > 0); + + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getRules", + args = {} + ) + public void testGetRules() throws ParseException { + String rule = "< a = b < c"; + RuleBasedCollator coll = new RuleBasedCollator(rule); + assertEquals(rule, coll.getRules()); + } + + /* + * Class under test for java.text.CollationElementIterator + * getCollationElementIterator(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCollationElementIterator", + args = {java.lang.String.class} + ) + public void testGetCollationElementIteratorString() throws Exception { + { + Locale locale = new Locale("es", "", "TRADITIONAL"); + RuleBasedCollator coll = (RuleBasedCollator) Collator + .getInstance(locale); + String source = "cha"; + CollationElementIterator iterator = coll + .getCollationElementIterator(source); + int[] e_offset = { 0, 1, 2 }; + int offset = iterator.getOffset(); + int i = 0; + assertEquals(e_offset[i++], offset); + while (offset != source.length() - 1) { + iterator.next(); + offset = iterator.getOffset(); + assertEquals(e_offset[i++], offset); + } + } + + { + Locale locale = new Locale("de", "DE"); + RuleBasedCollator coll = (RuleBasedCollator) Collator + .getInstance(locale); + String source = "\u00E6b"; + CollationElementIterator iterator = coll + .getCollationElementIterator(source); + int[] e_offset = { 0, 1, 1, 2 }; + int offset = iterator.getOffset(); + int i = 0; + assertEquals(e_offset[i++], offset); + while (offset != source.length()) { + iterator.next(); + offset = iterator.getOffset(); + assertEquals(e_offset[i++], offset); + } + } + // Regression for HARMONY-1352 + try { + new RuleBasedCollator("< a< b< c< d").getCollationElementIterator((String)null); + fail("NullPointerException expected"); + } catch (NullPointerException e) { + // expected + } + } + + /* + * Class under test for java.text.CollationElementIterator + * getCollationElementIterator(java.text.CharacterIterator) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "getCollationElementIterator", + args = {java.text.CharacterIterator.class} + ) + @KnownFailure("Succeeds against RI.") + public void testGetCollationElementIteratorCharacterIterator() throws Exception { + { + Locale locale = new Locale("es", "", "TRADITIONAL"); + RuleBasedCollator coll = (RuleBasedCollator) Collator + .getInstance(locale); + String text = "cha"; + StringCharacterIterator source = new StringCharacterIterator(text); + CollationElementIterator iterator = coll + .getCollationElementIterator(source); + int[] e_offset = { 0, 1, 2 }; + int offset = iterator.getOffset(); + int i = 0; + assertEquals(e_offset[i++], offset); + while (offset != text.length() - 1) { + iterator.next(); + offset = iterator.getOffset(); + assertEquals(e_offset[i], offset); + i++; + } + } + + { + Locale locale = new Locale("de", "DE"); + RuleBasedCollator coll = (RuleBasedCollator) Collator + .getInstance(locale); + String text = "\u00E6b"; + StringCharacterIterator source = new StringCharacterIterator(text); + CollationElementIterator iterator = coll + .getCollationElementIterator(source); + int[] e_offset = { 0, 1, 1, 2 }; + int offset = iterator.getOffset(); + int i = 0; + assertEquals(e_offset[i++], offset); + while (offset != text.length()) { + iterator.next(); + offset = iterator.getOffset(); + assertEquals(e_offset[i++], offset); + } + } + // Regression for HARMONY-1352 + try { + new RuleBasedCollator("< a< b< c< d").getCollationElementIterator((CharacterIterator)null); + fail("NullPointerException expected"); + } catch (NullPointerException e) { + // expected + } + } + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Doesn't verify setStrength method with PRIMARY, SECONDARY, TERTIARY or IDENTICAL values as a parameter; doesn't verify thatsetStrength method can throw IllegalArgumentException.", + method = "setStrength", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Doesn't verify setStrength method with PRIMARY, SECONDARY, TERTIARY or IDENTICAL values as a parameter; doesn't verify thatsetStrength method can throw IllegalArgumentException.", + method = "getStrength", + args = {} + ) + }) + public void testStrength() { + RuleBasedCollator coll = (RuleBasedCollator) Collator + .getInstance(Locale.US); + for (int i = 0; i < 4; i++) { + coll.setStrength(i); + assertEquals(i, coll.getStrength()); + } + + } + @TestTargets({ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setDecomposition", + args = {int.class} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getDecomposition", + args = {} + ) + }) + public void testDecomposition() { + RuleBasedCollator coll = (RuleBasedCollator) Collator + .getInstance(Locale.US); + + int [] decompositions = {Collator.NO_DECOMPOSITION, + Collator.CANONICAL_DECOMPOSITION}; + + for (int decom:decompositions) { + coll.setDecomposition(decom); + assertEquals(decom, coll.getDecomposition()); + } + + try { + coll.setDecomposition(-1); + fail("IllegalArgumentException was not thrown."); + } catch(IllegalArgumentException iae) { + //expected + } + } + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getInstance", + args = {} + ) + public void testCollator_GetInstance() { + Collator coll = Collator.getInstance(); + Object obj1 = "a"; + Object obj2 = "b"; + assertEquals(-1, coll.compare(obj1, obj2)); + + Collator.getInstance(); + assertFalse(coll.equals("A", "\uFF21")); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "getAvailableLocales", + args = {} + ) + @KnownFailure("Succeeds against RI.") + public void testGetAvaiableLocales() { + Locale[] locales = Collator.getAvailableLocales(); + boolean isUS = false; + for (int i = 0; i < locales.length; i++) { + if(locales[i].equals(Locale.US)) + isUS = true; + } + assertTrue("No Locale.US in the array.", isUS); + } + + // Test CollationKey + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCollationKey", + args = {java.lang.String.class} + ) + public void testCollationKey() { + Collator coll = Collator.getInstance(Locale.US); + String text = "abc"; + CollationKey key = coll.getCollationKey(text); + key.hashCode(); + + CollationKey key2 = coll.getCollationKey("abc"); + + assertEquals(0, key.compareTo(key2)); + } + + /** + * @tests java.text.RuleBasedCollator.RuleBasedCollator(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Verifies RuleBasedCollator(java.lang.String) constructor with null as a parameter.", + method = "RuleBasedCollator", + args = {java.lang.String.class} + ) + public void testNullPointerException() throws Exception { + // Regression for HARMONY-241 + try { + new RuleBasedCollator(null); + fail("Constructor RuleBasedCollator(null) " + + "should throw NullPointerException"); + } catch (NullPointerException e) {} + } + + /** + * @tests java.text.RuleBasedCollator.compare(java.lang.String, java.lang.String) + */ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Verifies null as parameters.", + method = "compare", + args = {java.lang.String.class, java.lang.String.class} + ) + public void testCompareNull() throws Exception { + // Regression for HARMONY-836 + try { + new RuleBasedCollator("< a").compare(null, null); + fail("RuleBasedCollator.compare(null, null) " + + "should throw NullPointerException"); + } catch (NullPointerException e) {} + } + + /** + * @tests java.text.RuleBasedCollator.RuleBasedCollator(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Verifies empty string as a parameter.", + method = "RuleBasedCollator", + args = {java.lang.String.class} + ) + @AndroidOnly("Android uses icu for collating. " + + "Icu has default UCA rules it uses to collate. " + + "To create a default instance with these rules an empty " + + "rule has to be passed to icu. This behavior is different " + + "from the RI which would throw an exception.") + public void testEmptyStringException() { + try { + RuleBasedCollator coll = new RuleBasedCollator(""); + assertTrue(coll.equals(new RuleBasedCollator(""))); + } catch (ParseException e) { + fail("Constructor RuleBasedCollator(\"\") " + + "should NOT throw ParseException."); + } + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/SimpleDateFormatTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/SimpleDateFormatTest.java new file mode 100644 index 0000000..821808f --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/SimpleDateFormatTest.java @@ -0,0 +1,1165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.AndroidOnly; +import dalvik.annotation.BrokenTest; +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargets; +import tests.support.Support_SimpleDateFormat; + +import java.text.DateFormat; +import java.text.DateFormatSymbols; +import java.text.FieldPosition; +import java.text.ParseException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.SimpleTimeZone; +import java.util.TimeZone; + +@TestTargetClass(SimpleDateFormat.class) +public class SimpleDateFormatTest extends junit.framework.TestCase { + + static SimpleDateFormat format = new SimpleDateFormat("", Locale.ENGLISH); + + static SimpleDateFormat pFormat = new SimpleDateFormat("", Locale.ENGLISH); + + static class TestFormat extends junit.framework.TestCase { + boolean testsFailed = false; + + public TestFormat(String name) { + super(name); + } + + public void test(String pattern, Calendar cal, String expected, + int field) { + StringBuffer buffer = new StringBuffer(); + FieldPosition position = new FieldPosition(field); + format.applyPattern(pattern); + format.format(cal.getTime(), buffer, position); + String result = buffer.toString(); + if (!System.getProperty("java.vendor", "None").substring(0, 3) + .equals("Sun")) { + assertTrue("Wrong format: \"" + pattern + "\" expected: " + + expected + " result: " + result, result + .equals(expected)); + assertTrue("Wrong begin position: " + pattern + " expected: " + + expected + " field: " + field, position + .getBeginIndex() == 1); + assertTrue("Wrong end position: " + pattern + " expected: " + + expected + " field: " + field, + position.getEndIndex() == result.length()); + } else { + // Print the failure but don't use assert as this + // will stop subsequent tests from running + if (!result.equals(expected)) { + System.out + .println("Wrong format: \"" + pattern + + "\" expected: " + expected + " result: " + + result); + testsFailed = true; + } + } + } + + public boolean testsFailed() { + return testsFailed; + } + + public void parse(String pattern, String input, Date expected, + int start, int end) { + pFormat.applyPattern(pattern); + ParsePosition position = new ParsePosition(start); + + Date result = pFormat.parse(input, position); + assertTrue("Wrong result: " + pattern + " input: " + input + + " expected: " + expected + " result: " + result, expected + .equals(result)); + assertTrue("Wrong end position: " + pattern + " input: " + input, + position.getIndex() == end); + } + + public void verifyFormatTimezone(String timeZoneId, String expected1, + String expected2, Date date) { + format.setTimeZone(SimpleTimeZone.getTimeZone(timeZoneId)); + format.applyPattern("z, zzzz"); + assertEquals("Test z for TimeZone : " + timeZoneId, expected1, + format.format(date)); + + format.applyPattern("Z, ZZZZ"); + assertEquals("Test Z for TimeZone : " + timeZoneId, expected2, + format.format(date)); + } + } + + /** + * @tests java.text.SimpleDateFormat#SimpleDateFormat() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "SimpleDateFormat", + args = {} + ) + public void test_Constructor() { + // Test for method java.text.SimpleDateFormat() + SimpleDateFormat f2 = new SimpleDateFormat(); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertTrue("Wrong default", f2.equals(DateFormat.getDateTimeInstance( + DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault()))); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + } + + /** + * @tests java.text.SimpleDateFormat#SimpleDateFormat(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "SimpleDateFormat", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() { + // Test for method java.text.SimpleDateFormat(java.lang.String) + SimpleDateFormat f2 = new SimpleDateFormat("yyyy"); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertEquals("Wrong pattern", "yyyy", f2.toPattern()); + assertTrue("Wrong locale", f2.equals(new SimpleDateFormat("yyyy", + Locale.getDefault()))); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols())); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + + // Invalid constructor value. + try { + new SimpleDateFormat( + "this is an invalid simple date format"); + fail("Expected test_ConstructorLjava_lang_String to throw IAE."); + } catch (IllegalArgumentException ex) { + // expected + } catch (Throwable ex) { + fail("Expected test_ConstructorLjava_lang_String to throw IAE, not " + + ex.getClass().getName()); + } + + // Null string value + try { + new SimpleDateFormat(null); + fail("Expected test_ConstructorLjava_lang_String to throw NPE."); + } catch (NullPointerException ex) { + // expected + } catch (Throwable ex) { + fail("Expected test_ConstructorLjava_lang_String to throw NPE, not " + + ex.getClass().getName()); + } + } + + /** + * @tests java.text.SimpleDateFormat#SimpleDateFormat(java.lang.String, + * java.text.DateFormatSymbols) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "SimpleDateFormat", + args = {java.lang.String.class, java.text.DateFormatSymbols.class} + ) + public void test_ConstructorLjava_lang_StringLjava_text_DateFormatSymbols() { + // Test for method java.text.SimpleDateFormat(java.lang.String, + // java.text.DateFormatSymbols) + DateFormatSymbols symbols = new DateFormatSymbols(Locale.ENGLISH); + symbols.setEras(new String[] { "Before", "After" }); + SimpleDateFormat f2 = new SimpleDateFormat("y'y'yy", symbols); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertEquals("Wrong pattern", "y'y'yy", f2.toPattern()); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals(symbols)); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + + try { + new SimpleDateFormat(null, symbols); + fail("NullPointerException was not thrown."); + } catch(NullPointerException npe) { + //expected + } + + try { + new SimpleDateFormat("eee", symbols); + fail("IllegalArgumentException was not thrown."); + } catch(IllegalArgumentException iae) { + //expected + } + } + + /** + * @tests java.text.SimpleDateFormat#SimpleDateFormat(java.lang.String, + * java.util.Locale) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "SimpleDateFormat", + args = {java.lang.String.class, java.util.Locale.class} + ) + public void test_ConstructorLjava_lang_StringLjava_util_Locale() { + // Test for method java.text.SimpleDateFormat(java.lang.String, + // java.util.Locale) + SimpleDateFormat f2 = new SimpleDateFormat("'yyyy' MM yy", + Locale.GERMAN); + assertTrue("Wrong class", f2.getClass() == SimpleDateFormat.class); + assertEquals("Wrong pattern", "'yyyy' MM yy", f2.toPattern()); + assertTrue("Wrong symbols", f2.getDateFormatSymbols().equals( + new DateFormatSymbols(Locale.GERMAN))); + assertTrue("Doesn't work", + f2.format(new Date()).getClass() == String.class); + + try { + new SimpleDateFormat(null, Locale.GERMAN); + fail("NullPointerException was not thrown."); + } catch(NullPointerException npe) { + //expected + } + try { + new SimpleDateFormat("eee", Locale.GERMAN); + fail("IllegalArgumentException was not thrown."); + } catch(IllegalArgumentException iae) { + //expected + } + } + + /** + * @tests java.text.SimpleDateFormat#applyLocalizedPattern(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "applyLocalizedPattern", + args = {java.lang.String.class} + ) + @AndroidOnly("ICU specific...") + public void test_applyLocalizedPatternLjava_lang_String() { + // Test for method void + // java.text.SimpleDateFormat.applyLocalizedPattern(java.lang.String) + SimpleDateFormat f2 = new SimpleDateFormat("y", new Locale("de", "CH")); + // BEGIN android-removed + // This test doesn't work like this. The cause lies inside of icu + // that doesn't support localized pattern characters anymore. So this + // test fails because the pattern template contains characters that are + // not part of the standard pattern returned for every locale. + // The default pattern characters are: GyMdkHmsSEDFwWahKzZ + // + // f2.applyLocalizedPattern("GuMtkHmsSEDFwWahKz"); + // String pattern = f2.toPattern(); + // assertTrue("Wrong pattern: " + pattern, pattern + // .equals("GyMdkHmsSEDFwWahKz")); + // + // test the new "Z" pattern char + // f2 = new SimpleDateFormat("y", new Locale("de", "CH")); + // f2.applyLocalizedPattern("G u M t Z"); + // pattern = f2.toPattern(); + // assertTrue("Wrong pattern: " + pattern, pattern.equals("G y M d Z")); + // END android-removed + + // test invalid patterns + try { + f2.applyLocalizedPattern("b"); + fail("Expected IllegalArgumentException for pattern with invalid pattern letter: b"); + } catch (IllegalArgumentException e) { + } + + try { + // BEGIN android-canged + f2.applyLocalizedPattern("u"); + fail("Expected IllegalArgumentException for pattern with invalid pattern letter: u"); + // END android-changed + } catch (IllegalArgumentException e) { + } + + try { + f2.applyLocalizedPattern("a '"); + fail("Expected IllegalArgumentException for pattern with unterminated quote: a '"); + } catch (IllegalArgumentException e) { + } + + try { + f2.applyLocalizedPattern(null); + fail("Expected NullPointerException for null pattern"); + } catch (NullPointerException e) { + } + } + + /** + * @tests java.text.SimpleDateFormat#applyPattern(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "applyPattern", + args = {java.lang.String.class} + ) + public void test_applyPatternLjava_lang_String() { + // Test for method void + // java.text.SimpleDateFormat.applyPattern(java.lang.String) + SimpleDateFormat f2 = new SimpleDateFormat("y", new Locale("de", "CH")); + // BEGIN android-changed + f2.applyPattern("GyMdkHmsSEDFwWahKzZ"); + assertEquals("Wrong pattern", "GyMdkHmsSEDFwWahKzZ", f2.toPattern()); + // END android-changed + + // test invalid patterns + try { + f2.applyPattern("b"); + fail("Expected IllegalArgumentException for pattern with invalid patter letter: b"); + } catch (IllegalArgumentException e) { + } + + try { + f2.applyPattern("u"); + fail("Expected IllegalArgumentException for pattern with invalid patter letter: u"); + } catch (IllegalArgumentException e) { + } + + try { + f2.applyPattern("a '"); + fail("Expected IllegalArgumentException for pattern with unterminated quote: a '"); + } catch (IllegalArgumentException e) { + } + + try { + f2.applyPattern(null); + fail("Expected NullPointerException for null pattern"); + } catch (NullPointerException e) { + } + } + + /** + * @tests java.text.SimpleDateFormat#clone() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + // Test for method java.lang.Object java.text.SimpleDateFormat.clone() + SimpleDateFormat f2 = new SimpleDateFormat(); + SimpleDateFormat clone = (SimpleDateFormat) f2.clone(); + assertTrue("Invalid clone", f2.equals(clone)); + clone.applyPattern("y"); + assertTrue("Format modified", !f2.equals(clone)); + clone = (SimpleDateFormat) f2.clone(); + // Date date = clone.get2DigitYearStart(); + // date.setTime(0); + // assertTrue("Equal after date change: " + + // f2.get2DigitYearStart().getTime() + " " + + // clone.get2DigitYearStart().getTime(), !f2.equals(clone)); + } + + /** + * @tests java.text.SimpleDateFormat#equals(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + // Test for method boolean + // java.text.SimpleDateFormat.equals(java.lang.Object) + SimpleDateFormat format = (SimpleDateFormat) DateFormat.getInstance(); + SimpleDateFormat clone = (SimpleDateFormat) format.clone(); + assertTrue("clone not equal", format.equals(clone)); + format.format(new Date()); + assertTrue("not equal after format", format.equals(clone)); + } + + /** + * @tests java.text.SimpleDateFormat#hashCode() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + SimpleDateFormat format = (SimpleDateFormat) DateFormat.getInstance(); + SimpleDateFormat clone = (SimpleDateFormat) format.clone(); + assertTrue("clone has not equal hash code", clone.hashCode() == format + .hashCode()); + format.format(new Date()); + assertTrue("clone has not equal hash code after format", clone + .hashCode() == format.hashCode()); + DateFormatSymbols symbols = new DateFormatSymbols(Locale.ENGLISH); + symbols.setEras(new String[] { "Before", "After" }); + SimpleDateFormat format2 = new SimpleDateFormat("y'y'yy", symbols); + assertFalse("objects has equal hash code", format2.hashCode() == format + .hashCode()); + } + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test.", + method = "SimpleDateFormat", + args = {} + ) + public void test_equals_afterFormat() { + // Regression test for HARMONY-209 + SimpleDateFormat df = new SimpleDateFormat(); + df.format(new Date()); + assertEquals(df, new SimpleDateFormat()); + } + + /** + * @tests java.text.SimpleDateFormat#formatToCharacterIterator(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "formatToCharacterIterator", + args = {java.lang.Object.class} + ) + public void test_formatToCharacterIteratorLjava_lang_Object() { + + try { + // Regression for HARMONY-466 + new SimpleDateFormat().formatToCharacterIterator(null); + fail("NullPointerException expected"); + } catch (NullPointerException e) { + // expected + } + + // Test for method formatToCharacterIterator(java.lang.Object) + new Support_SimpleDateFormat( + "test_formatToCharacterIteratorLjava_lang_Object") + .t_formatToCharacterIterator(); + } + + /** + * @tests java.text.SimpleDateFormat#format(java.util.Date, + * java.lang.StringBuffer, java.text.FieldPosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "format", + args = {java.util.Date.class, java.lang.StringBuffer.class, java.text.FieldPosition.class} + ) + @KnownFailure("SimpleDateFormat.format(Date date, " + + "StringBuffer toAppendTo, FieldPosition pos) " + + "return incorrect week number for \" W\" pattern. ") + public void test_formatLjava_util_DateLjava_lang_StringBufferLjava_text_FieldPosition() { + // Test for method java.lang.StringBuffer + // java.text.SimpleDateFormat.format(java.util.Date, + // java.lang.StringBuffer, java.text.FieldPosition) + + // Android doesn't support EST time zone + // new Support_SimpleDateFormat( + // "test_formatLjava_util_DateLjava_lang_StringBufferLjava_text_FieldPosition") + // .t_format_with_FieldPosition(); + + TestFormat test = new TestFormat( + "test_formatLjava_util_DateLjava_lang_StringBufferLjava_text_FieldPosition"); + + Calendar cal = new GregorianCalendar(1999, Calendar.JUNE, 2, 15, 3, 6); + test.test(" G", cal, " AD", DateFormat.ERA_FIELD); + test.test(" GG", cal, " AD", DateFormat.ERA_FIELD); + test.test(" GGG", cal, " AD", DateFormat.ERA_FIELD); + test.test(" G", new GregorianCalendar(-1999, Calendar.JUNE, 2), " BC", + DateFormat.ERA_FIELD); + + test.test(" y", cal, " 99", DateFormat.YEAR_FIELD); + test.test(" yy", cal, " 99", DateFormat.YEAR_FIELD); + test.test(" yy", new GregorianCalendar(2001, Calendar.JUNE, 2), " 01", + DateFormat.YEAR_FIELD); + test.test(" yy", new GregorianCalendar(2000, Calendar.JUNE, 2), " 00", + DateFormat.YEAR_FIELD); + test.test(" yyy", new GregorianCalendar(2000, Calendar.JUNE, 2), " 00", + DateFormat.YEAR_FIELD); + test.test(" yyy", cal, " 99", DateFormat.YEAR_FIELD); + test.test(" yyyy", cal, " 1999", DateFormat.YEAR_FIELD); + test.test(" yyyyy", cal, " 01999", DateFormat.YEAR_FIELD); + + test.test(" M", cal, " 6", DateFormat.MONTH_FIELD); + test.test(" M", new GregorianCalendar(1999, Calendar.NOVEMBER, 2), + " 11", DateFormat.MONTH_FIELD); + test.test(" MM", cal, " 06", DateFormat.MONTH_FIELD); + test.test(" MMM", cal, " Jun", DateFormat.MONTH_FIELD); + test.test(" MMMM", cal, " June", DateFormat.MONTH_FIELD); + test.test(" MMMMM", cal, " June", DateFormat.MONTH_FIELD); + + test.test(" d", cal, " 2", DateFormat.DATE_FIELD); + test.test(" d", new GregorianCalendar(1999, Calendar.NOVEMBER, 12), + " 12", DateFormat.DATE_FIELD); + test.test(" dd", cal, " 02", DateFormat.DATE_FIELD); + test.test(" dddd", cal, " 0002", DateFormat.DATE_FIELD); + + test.test(" h", cal, " 3", DateFormat.HOUR1_FIELD); + test.test(" h", new GregorianCalendar(1999, Calendar.NOVEMBER, 12), + " 12", DateFormat.HOUR1_FIELD); + test.test(" hh", cal, " 03", DateFormat.HOUR1_FIELD); + test.test(" hhhh", cal, " 0003", DateFormat.HOUR1_FIELD); + + test.test(" H", cal, " 15", DateFormat.HOUR_OF_DAY0_FIELD); + test.test(" H", + new GregorianCalendar(1999, Calendar.NOVEMBER, 12, 4, 0), " 4", + DateFormat.HOUR_OF_DAY0_FIELD); + test.test(" H", new GregorianCalendar(1999, Calendar.NOVEMBER, 12, 12, + 0), " 12", DateFormat.HOUR_OF_DAY0_FIELD); + test.test(" H", new GregorianCalendar(1999, Calendar.NOVEMBER, 12), + " 0", DateFormat.HOUR_OF_DAY0_FIELD); + test.test(" HH", cal, " 15", DateFormat.HOUR_OF_DAY0_FIELD); + test.test(" HHHH", cal, " 0015", DateFormat.HOUR_OF_DAY0_FIELD); + + test.test(" m", cal, " 3", DateFormat.MINUTE_FIELD); + test.test(" m", new GregorianCalendar(1999, Calendar.NOVEMBER, 12, 4, + 47), " 47", DateFormat.MINUTE_FIELD); + test.test(" mm", cal, " 03", DateFormat.MINUTE_FIELD); + test.test(" mmmm", cal, " 0003", DateFormat.MINUTE_FIELD); + + test.test(" s", cal, " 6", DateFormat.SECOND_FIELD); + test.test(" s", new GregorianCalendar(1999, Calendar.NOVEMBER, 12, 4, + 47, 13), " 13", DateFormat.SECOND_FIELD); + test.test(" ss", cal, " 06", DateFormat.SECOND_FIELD); + test.test(" ssss", cal, " 0006", DateFormat.SECOND_FIELD); + + test.test(" S", cal, " 0", DateFormat.MILLISECOND_FIELD); + Calendar temp = new GregorianCalendar(); + temp.set(Calendar.MILLISECOND, 961); + + test.test(" SS", temp, " 961", DateFormat.MILLISECOND_FIELD); + test.test(" SSSS", cal, " 0000", DateFormat.MILLISECOND_FIELD); + + test.test(" SS", cal, " 00", DateFormat.MILLISECOND_FIELD); + + test.test(" E", cal, " Wed", DateFormat.DAY_OF_WEEK_FIELD); + test.test(" EE", cal, " Wed", DateFormat.DAY_OF_WEEK_FIELD); + test.test(" EEE", cal, " Wed", DateFormat.DAY_OF_WEEK_FIELD); + test.test(" EEEE", cal, " Wednesday", DateFormat.DAY_OF_WEEK_FIELD); + test.test(" EEEEE", cal, " Wednesday", DateFormat.DAY_OF_WEEK_FIELD); + + test.test(" D", cal, " 153", DateFormat.DAY_OF_YEAR_FIELD); + test.test(" DD", cal, " 153", DateFormat.DAY_OF_YEAR_FIELD); + test.test(" DDDD", cal, " 0153", DateFormat.DAY_OF_YEAR_FIELD); + + test.test(" F", cal, " 1", DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD); + test.test(" F", new GregorianCalendar(1999, Calendar.NOVEMBER, 14), + " 2", DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD); + test.test(" FF", cal, " 01", DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD); + test.test(" FFFF", cal, " 0001", DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD); + + test.test(" w", cal, " 23", DateFormat.WEEK_OF_YEAR_FIELD); + test.test(" ww", cal, " 23", DateFormat.WEEK_OF_YEAR_FIELD); + test.test(" wwww", cal, " 0023", DateFormat.WEEK_OF_YEAR_FIELD); + + test.test(" W", cal, " 1", DateFormat.WEEK_OF_MONTH_FIELD); + test.test(" W", new GregorianCalendar(1999, Calendar.NOVEMBER, 14), + " 3", DateFormat.WEEK_OF_MONTH_FIELD); + test.test(" WW", cal, " 01", DateFormat.WEEK_OF_MONTH_FIELD); + test.test(" WWWW", cal, " 0001", DateFormat.WEEK_OF_MONTH_FIELD); + + test.test(" a", cal, " PM", DateFormat.AM_PM_FIELD); + test.test(" a", new GregorianCalendar(1999, Calendar.NOVEMBER, 14), + " AM", DateFormat.AM_PM_FIELD); + test.test(" a", new GregorianCalendar(1999, Calendar.NOVEMBER, 14, 12, + 0), " PM", DateFormat.AM_PM_FIELD); + test.test(" aa", cal, " PM", DateFormat.AM_PM_FIELD); + test.test(" aaa", cal, " PM", DateFormat.AM_PM_FIELD); + test.test(" aaaa", cal, " PM", DateFormat.AM_PM_FIELD); + test.test(" aaaaa", cal, " PM", DateFormat.AM_PM_FIELD); + + test.test(" k", cal, " 15", DateFormat.HOUR_OF_DAY1_FIELD); + test.test(" k", + new GregorianCalendar(1999, Calendar.NOVEMBER, 12, 4, 0), " 4", + DateFormat.HOUR_OF_DAY1_FIELD); + test.test(" k", new GregorianCalendar(1999, Calendar.NOVEMBER, 12, 12, + 0), " 12", DateFormat.HOUR_OF_DAY1_FIELD); + test.test(" k", new GregorianCalendar(1999, Calendar.NOVEMBER, 12), + " 24", DateFormat.HOUR_OF_DAY1_FIELD); + test.test(" kk", cal, " 15", DateFormat.HOUR_OF_DAY1_FIELD); + test.test(" kkkk", cal, " 0015", DateFormat.HOUR_OF_DAY1_FIELD); + + test.test(" K", cal, " 3", DateFormat.HOUR0_FIELD); + test.test(" K", new GregorianCalendar(1999, Calendar.NOVEMBER, 12), + " 0", DateFormat.HOUR0_FIELD); + test.test(" KK", cal, " 03", DateFormat.HOUR0_FIELD); + test.test(" KKKK", cal, " 0003", DateFormat.HOUR0_FIELD); + +// Android doesn't support EST/EDT time zones +// format.setTimeZone(TimeZone.getTimeZone("EST")); +// test.test(" z", cal, " EDT", DateFormat.TIMEZONE_FIELD); +// Calendar temp2 = new GregorianCalendar(1999, Calendar.JANUARY, 12); +// test.test(" z", temp2, " EST", DateFormat.TIMEZONE_FIELD); +// test.test(" zz", cal, " EDT", DateFormat.TIMEZONE_FIELD); +// test.test(" zzz", cal, " EDT", DateFormat.TIMEZONE_FIELD); +// test.test(" zzzz", cal, " Eastern Daylight Time", +// DateFormat.TIMEZONE_FIELD); +// test.test(" zzzz", temp2, " Eastern Standard Time", +// DateFormat.TIMEZONE_FIELD); +// test.test(" zzzzz", cal, " Eastern Daylight Time", +// DateFormat.TIMEZONE_FIELD); + + format.setTimeZone(new SimpleTimeZone(60000, "ONE MINUTE")); + test.test(" z", cal, " GMT+00:01", DateFormat.TIMEZONE_FIELD); + test.test(" zzzz", cal, " GMT+00:01", DateFormat.TIMEZONE_FIELD); + format.setTimeZone(new SimpleTimeZone(5400000, "ONE HOUR, THIRTY")); + test.test(" z", cal, " GMT+01:30", DateFormat.TIMEZONE_FIELD); + format + .setTimeZone(new SimpleTimeZone(-5400000, + "NEG ONE HOUR, THIRTY")); + test.test(" z", cal, " GMT-01:30", DateFormat.TIMEZONE_FIELD); + + format.applyPattern("'Mkz''':.@5"); + assertEquals("Wrong output", "Mkz':.@5", format.format(new Date())); + + //assertTrue("Tests failed", !test.testsFailed()); + + // Test invalid args to format. + SimpleDateFormat dateFormat = new SimpleDateFormat(); + try { + dateFormat.format(null, new StringBuffer(), new FieldPosition(1)); + fail("Expected test to throw NPE."); + } catch (NullPointerException ex) { + // expected + } catch (Throwable ex) { + fail("Expected test to throw NPE, not " + ex.getClass().getName()); + } + } + + /** + * @tests java.text.SimpleDateFormat#format(java.util.Date) + */ + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Verifies formatting of timezones.", + method = "setTimeZone", + args = {java.util.TimeZone.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Verifies formatting of timezones.", + method = "format", + args = {java.util.Date.class, java.lang.StringBuffer.class, java.text.FieldPosition.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Verifies formatting of timezones.", + method = "applyPattern", + args = {java.lang.String.class} + ) + }) + public void test_timeZoneFormatting() { + // tests specific to formatting of timezones + Date summerDate = new GregorianCalendar(1999, Calendar.JUNE, 2, 15, 3, + 6).getTime(); + Date winterDate = new GregorianCalendar(1999, Calendar.JANUARY, 12) + .getTime(); + + TestFormat test = new TestFormat( + "test_formatLjava_util_DateLjava_lang_StringBufferLjava_text_FieldPosition"); +// Android doesn't support this time zone +// test.verifyFormatTimezone("PST", "PDT, Pacific Daylight Time", +// "-0700, -0700", summerDate); +// test.verifyFormatTimezone("PST", "PST, Pacific Standard Time", +// "-0800, -0800", winterDate); + + test.verifyFormatTimezone("GMT-7", "GMT-07:00, GMT-07:00", + "-0700, -0700", summerDate); + test.verifyFormatTimezone("GMT-7", "GMT-07:00, GMT-07:00", + "-0700, -0700", winterDate); + + // Pacific/Kiritimati is one of the timezones supported only in mJava +// Android doesn't support this time zone +// test.verifyFormatTimezone("Pacific/Kiritimati", "LINT, Line Is. Time", +// "+1400, +1400", summerDate); +// test.verifyFormatTimezone("Pacific/Kiritimati", "LINT, Line Is. Time", +// "+1400, +1400", winterDate); + +// test.verifyFormatTimezone("EDT", "EDT, Eastern Daylight Time", +// "-0400, -0400", summerDate); +// test.verifyFormatTimezone("EST", "EST, Eastern Standard Time", +// "-0500, -0500", winterDate); + + test.verifyFormatTimezone("GMT+14", "GMT+14:00, GMT+14:00", + "+1400, +1400", summerDate); + test.verifyFormatTimezone("GMT+14", "GMT+14:00, GMT+14:00", + "+1400, +1400", winterDate); + } + + /** + * @tests java.text.SimpleDateFormat#get2DigitYearStart() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "get2DigitYearStart", + args = {} + ) + public void test_get2DigitYearStart() { + // Test for method java.util.Date + // java.text.SimpleDateFormat.get2DigitYearStart() + SimpleDateFormat f1 = new SimpleDateFormat("y"); + Date date = f1.get2DigitYearStart(); + Calendar cal = new GregorianCalendar(); + int year = cal.get(Calendar.YEAR); + cal.setTime(date); + assertTrue("Wrong default year start", + cal.get(Calendar.YEAR) == (year - 80)); + } + + /** + * @tests java.text.SimpleDateFormat#getDateFormatSymbols() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getDateFormatSymbols", + args = {} + ) + public void test_getDateFormatSymbols() { + // Test for method java.text.DateFormatSymbols + // java.text.SimpleDateFormat.getDateFormatSymbols() + SimpleDateFormat df = (SimpleDateFormat) DateFormat.getInstance(); + DateFormatSymbols dfs = df.getDateFormatSymbols(); + assertTrue("Symbols identical", dfs != df.getDateFormatSymbols()); + } + + /** + * @tests java.text.SimpleDateFormat#parse(java.lang.String, + * java.text.ParsePosition) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "parse", + args = {java.lang.String.class, java.text.ParsePosition.class} + ) + @AndroidOnly("ICU specific...") + public void test_parseLjava_lang_StringLjava_text_ParsePosition() { + // Test for method java.util.Date + // java.text.SimpleDateFormat.parse(java.lang.String, + // java.text.ParsePosition) + TestFormat test = new TestFormat( + "test_formatLjava_util_DateLjava_lang_StringBufferLjava_text_FieldPosition"); + + Calendar cal = new GregorianCalendar(1970, Calendar.JANUARY, 1); + Date time = cal.getTime(); + test.parse("h", " 12", time, 1, 3); + test.parse("H", " 0", time, 1, 2); + test.parse("k", " 24", time, 1, 3); + test.parse("K", " 0", time, 1, 2); + + cal = new GregorianCalendar(1970, Calendar.JANUARY, 1, 1, 0); + time = cal.getTime(); + test.parse("h", "1", time, 0, 1); + test.parse("H", "1 ", time, 0, 1); + test.parse("k", "1", time, 0, 1); + test.parse("K", "1", time, 0, 1); + + cal = new GregorianCalendar(1970, Calendar.JANUARY, 1, 11, 0); + time = cal.getTime(); + test.parse("h", "0011 ", time, 0, 4); + test.parse("K", "11", time, 0, 2); + cal = new GregorianCalendar(1970, Calendar.JANUARY, 1, 23, 0); + time = cal.getTime(); + test.parse("H", "23", time, 0, 2); + test.parse("k", "23", time, 0, 2); + + test.parse("h a", " 3 AM", new GregorianCalendar(1970, + Calendar.JANUARY, 1, 3, 0).getTime(), 1, 5); + test.parse("K a", " 3 pm ", new GregorianCalendar(1970, + Calendar.JANUARY, 1, 15, 0).getTime(), 1, 5); + test.parse("m:s", "0:59 ", new GregorianCalendar(1970, + Calendar.JANUARY, 1, 0, 0, 59).getTime(), 0, 4); + test.parse("m:s", "59:0", new GregorianCalendar(1970, Calendar.JANUARY, + 1, 0, 59, 0).getTime(), 0, 4); + test.parse("ms", "059", new GregorianCalendar(1970, Calendar.JANUARY, + 1, 0, 0, 59).getTime(), 0, 3); + + cal = new GregorianCalendar(1970, Calendar.JANUARY, 1); + test.parse("S", "0", cal.getTime(), 0, 1); + cal.setTimeZone(TimeZone.getTimeZone("HST")); + cal.set(Calendar.MILLISECOND, 999); + test.parse("S z", "999 HST", cal.getTime(), 0, 7); + + cal = new GregorianCalendar(1970, Calendar.JANUARY, 1); + cal.set(Calendar.ERA, GregorianCalendar.BC); + + test.parse("G", "Bc ", cal.getTime(), 0, 2); + + test.parse("y", "00", new GregorianCalendar(2000, Calendar.JANUARY, 1) + .getTime(), 0, 2); + test.parse("y", "99", new GregorianCalendar(1999, Calendar.JANUARY, 1) + .getTime(), 0, 2); + test.parse("y", "1", new GregorianCalendar(1, Calendar.JANUARY, 1) + .getTime(), 0, 1); + test.parse("y", "-1", new GregorianCalendar(-1, Calendar.JANUARY, 1) + .getTime(), 0, 2); + test.parse("y", "001", new GregorianCalendar(1, Calendar.JANUARY, 1) + .getTime(), 0, 3); + test.parse("y", "2005", + new GregorianCalendar(2005, Calendar.JANUARY, 1).getTime(), 0, + 4); + test.parse("yy", "00", new GregorianCalendar(2000, Calendar.JANUARY, 1) + .getTime(), 0, 2); + test.parse("yy", "99", new GregorianCalendar(1999, Calendar.JANUARY, 1) + .getTime(), 0, 2); + test.parse("yy", "1", new GregorianCalendar(1, Calendar.JANUARY, 1) + .getTime(), 0, 1); + test.parse("yy", "-1", new GregorianCalendar(-1, Calendar.JANUARY, 1) + .getTime(), 0, 2); + test.parse("yy", "001", new GregorianCalendar(1, Calendar.JANUARY, 1) + .getTime(), 0, 3); + test.parse("yy", "2005", new GregorianCalendar(2005, Calendar.JANUARY, + 1).getTime(), 0, 4); + test.parse("yyy", "99", new GregorianCalendar(99, Calendar.JANUARY, 1) + .getTime(), 0, 2); + test.parse("yyy", "1", new GregorianCalendar(1, Calendar.JANUARY, 1) + .getTime(), 0, 1); + test.parse("yyy", "-1", new GregorianCalendar(-1, Calendar.JANUARY, 1) + .getTime(), 0, 2); + test.parse("yyy", "001", new GregorianCalendar(1, Calendar.JANUARY, 1) + .getTime(), 0, 3); + test.parse("yyy", "2005", new GregorianCalendar(2005, Calendar.JANUARY, + 1).getTime(), 0, 4); + test.parse("yyyy", "99", new GregorianCalendar(99, Calendar.JANUARY, 1) + .getTime(), 0, 2); + test.parse("yyyy", " 1999", new GregorianCalendar(1999, + Calendar.JANUARY, 1).getTime(), 2, 6); + test.parse("MM'M'", "4M", + new GregorianCalendar(1970, Calendar.APRIL, 1).getTime(), 0, 2); + test.parse("MMM", "Feb", new GregorianCalendar(1970, Calendar.FEBRUARY, + 1).getTime(), 0, 3); + test.parse("MMMM d", "April 14 ", new GregorianCalendar(1970, + Calendar.APRIL, 14).getTime(), 0, 8); + test.parse("MMMMd", "April14 ", new GregorianCalendar(1970, + Calendar.APRIL, 14).getTime(), 0, 7); + test.parse("E w", "Mon 12", new GregorianCalendar(1970, Calendar.MARCH, + 16).getTime(), 0, 6); + test.parse("Ew", "Mon12", new GregorianCalendar(1970, Calendar.MARCH, + 16).getTime(), 0, 5); + test.parse("M EE ''W", "5 Tue '2", new GregorianCalendar(1970, + Calendar.MAY, 5).getTime(), 0, 8); + test.parse("MEE''W", "5Tue'2", new GregorianCalendar(1970, + Calendar.MAY, 5).getTime(), 0, 6); + test.parse("MMM EEE F", " JUL Sunday 3", new GregorianCalendar(1970, + Calendar.JULY, 19).getTime(), 1, 13); + test.parse("MMMEEEF", " JULSunday3", new GregorianCalendar(1970, + Calendar.JULY, 19).getTime(), 1, 11); + + cal = new GregorianCalendar(1970, Calendar.JANUARY, 1); + cal.setTimeZone(TimeZone.getTimeZone("GMT+0:1")); + cal.set(Calendar.DAY_OF_YEAR, 243); + test.parse("D z", "243 GMT+0:0", cal.getTime(), 0, 11); + cal.setTimeZone(TimeZone.getTimeZone("EST")); + cal.set(1970, Calendar.JANUARY, 1, 4, 30); + test.parse("h:m z", "4:30 GMT-5 ", cal.getTime(), 0, 10); + test.parse("h z", "14 GMT-24 ", new Date(51840000), 0, 9); + test.parse("h z", "14 GMT-23 ", new Date(133200000), 0, 9); + test.parse("h z", "14 GMT-0001 ", new Date(54000000), 0, 11); + test.parse("h z", "14 GMT+24 ", new Date(48960000), 0, 9); + test.parse("h z", "14 GMT+23 ", new Date(-32400000), 0, 9); + test.parse("h z", "14 GMT+0001 ", new Date(46800000), 0, 11); + test.parse("h z", "14 +0001 ", new Date(46800000), 0, 8); + test.parse("h z", "14 -0001 ", new Date(54000000), 0, 8); + + test.parse("yyyyMMddHHmmss", "19990913171901", new GregorianCalendar( + 1999, Calendar.SEPTEMBER, 13, 17, 19, 01).getTime(), 0, 14); + + Date d = new Date(1015822800000L); + SimpleDateFormat df = new SimpleDateFormat("", new Locale("en", "US")); + df.setTimeZone(TimeZone.getTimeZone("EST")); + + try { + df.applyPattern("dd MMMM yyyy EEEE"); + String output = df.format(d); + Date date = df.parse(output); + assertTrue("Invalid result 1: " + date, d.equals(date)); + + df.applyPattern("dd MMMM yyyy F"); + output = df.format(d); + date = df.parse(output); + assertTrue("Invalid result 2: " + date, d.equals(date)); + + df.applyPattern("dd MMMM yyyy w"); + output = df.format(d); + date = df.parse(output); + assertTrue("Invalid result 3: " + date, d.equals(date)); + + df.applyPattern("dd MMMM yyyy W"); + output = df.format(d); + date = df.parse(output); + assertTrue("Invalid result 4: " + date, d.equals(date)); + + df.applyPattern("dd MMMM yyyy D"); + date = df.parse("5 January 2002 70"); + assertTrue("Invalid result 5: " + date, d.equals(date)); + + df.applyPattern("W w dd MMMM yyyy EEEE"); + output = df.format(d); + date = df.parse("3 12 5 March 2002 Monday"); + assertTrue("Invalid result 6: " + date, d.equals(date)); + + df.applyPattern("w W dd MMMM yyyy EEEE"); + output = df.format(d); + date = df.parse("12 3 5 March 2002 Monday"); + assertTrue("Invalid result 6a: " + date, d.equals(date)); + + df.applyPattern("F dd MMMM yyyy EEEE"); + output = df.format(d); + date = df.parse("2 5 March 2002 Monday"); + assertTrue("Invalid result 7: " + date, d.equals(date)); + + df.applyPattern("w dd MMMM yyyy EEEE"); + output = df.format(d); + date = df.parse("11 5 January 2002 Monday"); + assertTrue("Invalid result 8: " + date, d.equals(date)); + + df.applyPattern("w dd yyyy EEEE MMMM"); + output = df.format(d); + date = df.parse("11 5 2002 Monday January"); + assertTrue("Invalid result 9: " + date, d.equals(date)); + + df.applyPattern("w yyyy EEEE MMMM dd"); + output = df.format(d); + date = df.parse("17 2002 Monday March 11"); + assertTrue("Invalid result 10: " + date, d.equals(date)); + + df.applyPattern("dd D yyyy MMMM"); + output = df.format(d); + date = df.parse("5 70 2002 January"); + assertTrue("Invalid result 11: " + date, d.equals(date)); + + df.applyPattern("D dd yyyy MMMM"); + output = df.format(d); + date = df.parse("240 11 2002 March"); + assertTrue("Invalid result 12: " + date, d.equals(date)); + } catch (ParseException e) { + fail("unexpected: " + e); + } + + try { + format.parse("240 11 2002 March", null); + fail("ParsePosition is null: NullPointerException was not thrown."); + } catch(NullPointerException pe) { + //expected + } + + try { + format.parse(null, new ParsePosition(0)); + fail("String is null: NullPointerException was not thrown."); + } catch(NullPointerException pe) { + //expected + } + } + + /** + * @tests java.text.SimpleDateFormat#set2DigitYearStart(java.util.Date) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "set2DigitYearStart", + args = {java.util.Date.class} + ) + public void test_set2DigitYearStartLjava_util_Date() { + // Test for method void + // java.text.SimpleDateFormat.set2DigitYearStart(java.util.Date) + SimpleDateFormat f1 = new SimpleDateFormat("yy"); + f1.set2DigitYearStart(new GregorianCalendar(1950, Calendar.JANUARY, 1) + .getTime()); + Calendar cal = new GregorianCalendar(); + try { + cal.setTime(f1.parse("49")); + assertEquals("Incorrect year 2049", 2049, cal.get(Calendar.YEAR)); + cal.setTime(f1.parse("50")); + int year = cal.get(Calendar.YEAR); + assertTrue("Incorrect year 1950: " + year, year == 1950); + f1.applyPattern("y"); + cal.setTime(f1.parse("00")); + assertEquals("Incorrect year 2000", 2000, cal.get(Calendar.YEAR)); + f1.applyPattern("yyy"); + cal.setTime(f1.parse("50")); + assertEquals("Incorrect year 50", 50, cal.get(Calendar.YEAR)); + } catch (ParseException e) { + fail("ParseException"); + } + } + + /** + * @tests java.text.SimpleDateFormat#setDateFormatSymbols(java.text.DateFormatSymbols) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setDateFormatSymbols", + args = {java.text.DateFormatSymbols.class} + ) + public void test_setDateFormatSymbolsLjava_text_DateFormatSymbols() { + // Test for method void + // java.text.SimpleDateFormat.setDateFormatSymbols(java.text.DateFormatSymbols) + SimpleDateFormat f1 = new SimpleDateFormat("a"); + DateFormatSymbols symbols = new DateFormatSymbols(); + symbols.setAmPmStrings(new String[] { "morning", "night" }); + f1.setDateFormatSymbols(symbols); + DateFormatSymbols newSym = f1.getDateFormatSymbols(); + assertTrue("Set incorrectly", newSym.equals(symbols)); + assertTrue("Not a clone", f1.getDateFormatSymbols() != symbols); + String result = f1.format(new GregorianCalendar(1999, Calendar.JUNE, + 12, 3, 0).getTime()); + assertEquals("Incorrect symbols used", "morning", result); + symbols.setEras(new String[] { "before", "after" }); + assertTrue("Identical symbols", !f1.getDateFormatSymbols().equals( + symbols)); + + try { + f1.setDateFormatSymbols(null); + fail("NullPointerException was not thrown."); + } catch(NullPointerException npe) { + //expected + } + } + + /** + * @tests java.text.SimpleDateFormat#toLocalizedPattern() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "toLocalizedPattern", + args = {} + ) + @AndroidOnly("ICU specific...") + public void test_toLocalizedPattern() { + // BEGIN android-changed + // Test for method java.lang.String + // java.text.SimpleDateFormat.toLocalizedPattern() + SimpleDateFormat f2 = new SimpleDateFormat("GyMdkHmsSEDFwWahKzZ", + new Locale("de", "CH")); + String pattern = f2.toLocalizedPattern(); + // the default localized pattern characters are the same for all locales + // since icu has droped support for this. the default pattern characters + // are these: GyMdkHmsSEDFwWahKz + // + // assertTrue("Wrong pattern: " + pattern, pattern + // .equals("GuMtkHmsSEDFwWahKz")); + assertTrue("Wrong pattern: " + pattern, pattern + .equals("GyMdkHmsSEDFwWahKzZ")); + + + // test the new "Z" pattern char + f2 = new SimpleDateFormat("G y M d Z", new Locale("de", "CH")); + pattern = f2.toLocalizedPattern(); + // assertTrue("Wrong pattern: " + pattern, pattern.equals("G u M t Z")); + assertTrue("Wrong pattern: " + pattern, pattern.equals("G y M d Z")); + // END android-changed + } + + /** + * @tests java.text.SimpleDateFormat#toPattern() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "toPattern", + args = {} + ) + public void test_toPattern() { + String pattern = "yyyy mm dd"; + SimpleDateFormat f = new SimpleDateFormat(pattern); + assertEquals("Wrong pattern: " + pattern, pattern, f.toPattern()); + + pattern = "GyMdkHmsSEDFwWahKz"; + f = new SimpleDateFormat("GyMdkHmsSEDFwWahKz", new Locale("de", "CH")); + assertTrue("Wrong pattern: " + pattern, f.toPattern().equals(pattern)); + + pattern = "G y M d Z"; + f = new SimpleDateFormat(pattern, new Locale("de", "CH")); + pattern = f.toPattern(); + assertTrue("Wrong pattern: " + pattern, f.toPattern().equals(pattern)); + } + + /** + * @tests java.text.SimpleDateFormat#parse(java.lang.String, + * java.text.ParsePosition) + */ + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Regression test.", + method = "parse", + args = {java.lang.String.class, java.text.ParsePosition.class} + ) + public void test_parse_with_spaces() { + // Regression for HARMONY-502 + SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss"); + df.setLenient(false); + + char allowed_chars[] = { 0x9, 0x20 }; + String allowed_char_names[] = { "tab", "space" }; + for (int i = 0; i < allowed_chars.length; i++) { + Date expected = new GregorianCalendar(1970, Calendar.JANUARY, 1, 9, + 7, 6).getTime(); + ParsePosition pp = new ParsePosition(0); + Date d = df.parse(allowed_chars[i] + "9:07:06", pp); + assertNotNull("hour may be prefixed by " + allowed_char_names[i], d); + assertEquals(expected, d); + + pp = new ParsePosition(0); + d = df.parse("09:" + allowed_chars[i] + "7:06", pp); + assertNotNull("minute may be prefixed by " + allowed_char_names[i], + d); + assertEquals(expected, d); + + pp = new ParsePosition(0); + d = df.parse("09:07:" + allowed_chars[i] + "6", pp); + assertNotNull("second may be prefixed by " + allowed_char_names[i], + d); + assertEquals(expected, d); + } + + char not_allowed_chars[] = { + // whitespace + 0x1c, 0x1d, 0x1e, 0x1f, 0xa, 0xb, 0xc, 0xd, 0x2001, 0x2002, + 0x2003, 0x2004, 0x2005, 0x2006, 0x2008, 0x2009, 0x200a, 0x200b, + 0x2028, 0x2029, 0x3000, + // non-breaking space + 0xA0, 0x2007, 0x202F }; + + for (int i = 0; i < not_allowed_chars.length; i++) { + + ParsePosition pp = new ParsePosition(0); + Date d = df.parse(not_allowed_chars[i] + "9:07", pp); + assertNull(d); + + pp = new ParsePosition(0); + d = df.parse("09:" + not_allowed_chars[i] + "7", pp); + assertNull(d); + + pp = new ParsePosition(0); + d = df.parse("09:07:" + not_allowed_chars[i] + "6", pp); + assertNull(d); + } + } +} diff --git a/text/src/test/java/org/apache/harmony/text/tests/java/text/StringCharacterIteratorTest.java b/text/src/test/java/org/apache/harmony/text/tests/java/text/StringCharacterIteratorTest.java new file mode 100644 index 0000000..df68dde --- /dev/null +++ b/text/src/test/java/org/apache/harmony/text/tests/java/text/StringCharacterIteratorTest.java @@ -0,0 +1,667 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.text.tests.java.text; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; + +@TestTargetClass(StringCharacterIterator.class) +public class StringCharacterIteratorTest extends TestCase { + + /** + * @tests java.text.StringCharacterIterator.StringCharacterIterator(String, + * int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "StringCharacterIterator", + args = {java.lang.String.class, int.class} + ) + public void test_ConstructorI() { + assertNotNull(new StringCharacterIterator("value", 0)); + assertNotNull(new StringCharacterIterator("value", "value".length())); + assertNotNull(new StringCharacterIterator("", 0)); + try { + new StringCharacterIterator(null, 0); + fail("Assert 0: no null pointer"); + } catch (NullPointerException e) { + // expected + } + + try { + new StringCharacterIterator("value", -1); + fail("Assert 1: no illegal argument"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + new StringCharacterIterator("value", "value".length() + 1); + fail("Assert 2: no illegal argument"); + } catch (IllegalArgumentException e) { + // expected + } + } + + /** + * @tests java.text.StringCharacterIterator(String, int, int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "StringCharacterIterator", + args = {java.lang.String.class, int.class, int.class, int.class} + ) + public void test_ConstructorIII() { + assertNotNull(new StringCharacterIterator("value", 0, "value".length(), + 0)); + assertNotNull(new StringCharacterIterator("value", 0, "value".length(), + 1)); + assertNotNull(new StringCharacterIterator("", 0, 0, 0)); + + try { + new StringCharacterIterator(null, 0, 0, 0); + fail("no null pointer"); + } catch (NullPointerException e) { + // Expected + } + + try { + new StringCharacterIterator("value", -1, "value".length(), 0); + fail("no illegal argument: invalid begin"); + } catch (IllegalArgumentException e) { + // Expected + } + + try { + new StringCharacterIterator("value", 0, "value".length() + 1, 0); + fail("no illegal argument: invalid end"); + } catch (IllegalArgumentException e) { + // Expected + } + + try { + new StringCharacterIterator("value", 2, 1, 0); + fail("no illegal argument: start greater than end"); + } catch (IllegalArgumentException e) { + // Expected + } + + try { + new StringCharacterIterator("value", 2, 1, 2); + fail("no illegal argument: start greater than end"); + } catch (IllegalArgumentException e) { + // Expected + } + + try { + new StringCharacterIterator("value", 2, 4, 1); + fail("no illegal argument: location greater than start"); + } catch (IllegalArgumentException e) { + // Expected + } + + try { + new StringCharacterIterator("value", 0, 2, 3); + fail("no illegal argument: location greater than start"); + } catch (IllegalArgumentException e) { + // Expected + } + } + + /** + * @tests java.text.StringCharacterIterator.equals(Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + StringCharacterIterator sci0 = new StringCharacterIterator("fixture"); + assertEquals(sci0, sci0); + assertFalse(sci0.equals(null)); + assertFalse(sci0.equals("fixture")); + + StringCharacterIterator sci1 = new StringCharacterIterator("fixture"); + assertEquals(sci0, sci1); + + sci1.next(); + assertFalse(sci0.equals(sci1)); + sci0.next(); + assertEquals(sci0, sci1); + + StringCharacterIterator it1 = new StringCharacterIterator("testing", 2, + 6, 4); + StringCharacterIterator it2 = new StringCharacterIterator("xxstinx", 2, + 6, 4); + assertTrue("Range is equal", !it1.equals(it2)); + StringCharacterIterator it3 = new StringCharacterIterator("testing", 2, + 6, 2); + it3.setIndex(4); + assertTrue("Not equal", it1.equals(it3)); + } + + /** + * @tests java.text.StringCharacterIterator.clone() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + StringCharacterIterator sci0 = new StringCharacterIterator("fixture"); + assertSame(sci0, sci0); + StringCharacterIterator sci1 = (StringCharacterIterator) sci0.clone(); + assertNotSame(sci0, sci1); + assertEquals(sci0, sci1); + + StringCharacterIterator it = new StringCharacterIterator("testing", 2, + 6, 4); + StringCharacterIterator clone = (StringCharacterIterator) it.clone(); + assertTrue("Clone not equal", it.equals(clone)); + } + + /** + * @tests java.text.StringCharacterIterator.current() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "current", + args = {} + ) + public void test_current() { + StringCharacterIterator fixture = new StringCharacterIterator("fixture"); + assertEquals('f', fixture.current()); + fixture.next(); + assertEquals('i', fixture.current()); + + StringCharacterIterator it = new StringCharacterIterator("testing", 2, + 6, 4); + assertEquals("Wrong current char", 'i', it.current()); + it.next(); + it.next(); + assertEquals("Doesn't return DONE", StringCharacterIterator.DONE, + it.current()); + it.next(); + assertEquals("Doesn't return DONE after next()", + StringCharacterIterator.DONE, + it.current()); + } + + /** + * @tests java.text.StringCharacterIterator.first() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "first", + args = {} + ) + public void test_first() { + StringCharacterIterator fixture = new StringCharacterIterator("fixture"); + assertEquals('f', fixture.first()); + fixture.next(); + assertEquals('f', fixture.first()); + fixture = new StringCharacterIterator("fixture", 1); + assertEquals('f', fixture.first()); + fixture = new StringCharacterIterator("fixture", 1, "fixture".length(), + 2); + assertEquals('i', fixture.first()); + + StringCharacterIterator it1 = new StringCharacterIterator("testing", 2, + 6, 4); + assertEquals("Wrong first char", 's', it1.first()); + assertEquals("Wrong next char", 't', it1.next()); + it1 = new StringCharacterIterator("testing", 2, 2, 2); + assertTrue("Not DONE", it1.first() == CharacterIterator.DONE); + } + + /** + * @tests java.text.StringCharacterIterator.getBeginIndex() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getBeginIndex", + args = {} + ) + public void test_getBeginIndex() { + StringCharacterIterator fixture = new StringCharacterIterator("fixture"); + assertEquals(0, fixture.getBeginIndex()); + fixture = new StringCharacterIterator("fixture", 1); + assertEquals(0, fixture.getBeginIndex()); + fixture = new StringCharacterIterator("fixture", 1, "fixture".length(), + 2); + assertEquals(1, fixture.getBeginIndex()); + + StringCharacterIterator it1 = new StringCharacterIterator("testing", 2, + 6, 4); + assertEquals("Wrong begin index 2", 2, it1.getBeginIndex()); + } + + /** + * @tests java.text.StringCharacterIterator.getEndIndex() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getEndIndex", + args = {} + ) + public void test_getEndIndex() { + StringCharacterIterator fixture = new StringCharacterIterator("fixture"); + assertEquals("fixture".length(), fixture.getEndIndex()); + fixture = new StringCharacterIterator("fixture", 1); + assertEquals("fixture".length(), fixture.getEndIndex()); + fixture = new StringCharacterIterator("fixture", 1, "fixture".length(), + 2); + assertEquals("fixture".length(), fixture.getEndIndex()); + fixture = new StringCharacterIterator("fixture", 1, 4, 2); + assertEquals(4, fixture.getEndIndex()); + + StringCharacterIterator it1 = new StringCharacterIterator("testing", 2, + 6, 4); + assertEquals("Wrong end index 6", 6, it1.getEndIndex()); + } + + /** + * @tests java.text.StringCharacterIterator.getIndex() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getIndex", + args = {} + ) + public void testGetIndex() { + StringCharacterIterator fixture = new StringCharacterIterator("fixture"); + assertEquals(0, fixture.getIndex()); + fixture = new StringCharacterIterator("fixture", 1); + assertEquals(1, fixture.getIndex()); + fixture = new StringCharacterIterator("fixture", 1, "fixture".length(), + 2); + assertEquals(2, fixture.getIndex()); + fixture = new StringCharacterIterator("fixture", 1, 4, 2); + assertEquals(2, fixture.getIndex()); + } + + /** + * @tests java.text.StringCharacterIterator.last() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "last", + args = {} + ) + public void testLast() { + StringCharacterIterator fixture = new StringCharacterIterator("fixture"); + assertEquals('e', fixture.last()); + fixture.next(); + assertEquals('e', fixture.last()); + fixture = new StringCharacterIterator("fixture", 1); + assertEquals('e', fixture.last()); + fixture = new StringCharacterIterator("fixture", 1, "fixture".length(), + 2); + assertEquals('e', fixture.last()); + fixture = new StringCharacterIterator("fixture", 1, 4, 2); + assertEquals('t', fixture.last()); + } + + /** + * @tests java.text.StringCharacterIterator.next() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "next", + args = {} + ) + public void test_next() { + StringCharacterIterator fixture = new StringCharacterIterator("fixture"); + assertEquals(0, fixture.getIndex()); + assertEquals('i', fixture.next()); + assertEquals(1, fixture.getIndex()); + assertEquals('x', fixture.next()); + assertEquals(2, fixture.getIndex()); + assertEquals('t', fixture.next()); + assertEquals(3, fixture.getIndex()); + assertEquals('u', fixture.next()); + assertEquals(4, fixture.getIndex()); + assertEquals('r', fixture.next()); + assertEquals(5, fixture.getIndex()); + assertEquals('e', fixture.next()); + assertEquals(6, fixture.getIndex()); + assertEquals(CharacterIterator.DONE, fixture.next()); + assertEquals(7, fixture.getIndex()); + assertEquals(CharacterIterator.DONE, fixture.next()); + assertEquals(7, fixture.getIndex()); + assertEquals(CharacterIterator.DONE, fixture.next()); + assertEquals(7, fixture.getIndex()); + + StringCharacterIterator it1 = new StringCharacterIterator("testing", 2, + 6, 3); + char result = it1.next(); + assertEquals("Wrong next char1", 'i', result); + assertEquals("Wrong next char2", 'n', it1.next()); + assertTrue("Wrong next char3", it1.next() == CharacterIterator.DONE); + assertTrue("Wrong next char4", it1.next() == CharacterIterator.DONE); + int index = it1.getIndex(); + assertEquals("Wrong index", 6, index); + assertTrue("Wrong current char", + it1.current() == CharacterIterator.DONE); + } + + /** + * @tests java.text.StringCharacterIterator.previous() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "previous", + args = {} + ) + public void test_previous() { + StringCharacterIterator fixture = new StringCharacterIterator("fixture"); + assertEquals(CharacterIterator.DONE, fixture.previous()); + assertEquals('i', fixture.next()); + assertEquals('x', fixture.next()); + assertEquals('t', fixture.next()); + assertEquals('u', fixture.next()); + assertEquals('r', fixture.next()); + assertEquals('e', fixture.next()); + assertEquals(CharacterIterator.DONE, fixture.next()); + assertEquals(CharacterIterator.DONE, fixture.next()); + assertEquals(CharacterIterator.DONE, fixture.next()); + assertEquals(7, fixture.getIndex()); + assertEquals('e', fixture.previous()); + assertEquals(6, fixture.getIndex()); + assertEquals('r', fixture.previous()); + assertEquals(5, fixture.getIndex()); + assertEquals('u', fixture.previous()); + assertEquals(4, fixture.getIndex()); + assertEquals('t', fixture.previous()); + assertEquals(3, fixture.getIndex()); + assertEquals('x', fixture.previous()); + assertEquals(2, fixture.getIndex()); + assertEquals('i', fixture.previous()); + assertEquals(1, fixture.getIndex()); + assertEquals('f', fixture.previous()); + assertEquals(0, fixture.getIndex()); + assertEquals(CharacterIterator.DONE, fixture.previous()); + assertEquals(0, fixture.getIndex()); + + StringCharacterIterator it1 = new StringCharacterIterator("testing", 2, + 6, 4); + assertEquals("Wrong previous char1", 't', it1.previous()); + assertEquals("Wrong previous char2", 's', it1.previous()); + assertTrue("Wrong previous char3", + it1.previous() == CharacterIterator.DONE); + assertTrue("Wrong previous char4", + it1.previous() == CharacterIterator.DONE); + assertEquals("Wrong index", 2, it1.getIndex()); + assertEquals("Wrong current char", 's', it1.current()); + } + + /** + * @tests java.text.StringCharacterIterator.setIndex(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setIndex", + args = {int.class} + ) + public void test_setIndex() { + StringCharacterIterator fixture = new StringCharacterIterator("fixture"); + while (fixture.next() != CharacterIterator.DONE) { + // empty + } + assertEquals("fixture".length(), fixture.getIndex()); + fixture.setIndex(0); + assertEquals(0, fixture.getIndex()); + assertEquals('f', fixture.current()); + fixture.setIndex("fixture".length() - 1); + assertEquals('e', fixture.current()); + try { + fixture.setIndex(-1); + fail("no illegal argument"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + fixture.setIndex("fixture".length() + 1); + fail("no illegal argument"); + } catch (IllegalArgumentException e) { + // expected + } + } + + /** + * @tests java.text.StringCharacterIterator.setText(String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setText", + args = {java.lang.String.class} + ) + public void test_setText() { + StringCharacterIterator fixture = new StringCharacterIterator("fixture"); + fixture.setText("fix"); + assertEquals('f', fixture.current()); + assertEquals('x', fixture.last()); + + try { + fixture.setText(null); + fail("no null pointer"); + } catch (NullPointerException e) { + // expected + } + } + + /** + * @tests java.text.StringCharacterIterator#StringCharacterIterator(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "StringCharacterIterator", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() { + assertNotNull(new StringCharacterIterator("value")); + assertNotNull(new StringCharacterIterator("")); + try { + new StringCharacterIterator(null); + fail("Assert 0: no null pointer"); + } catch (NullPointerException e) { + // expected + } + + StringCharacterIterator it = new StringCharacterIterator("testing"); + assertEquals("Wrong begin index", 0, it.getBeginIndex()); + assertEquals("Wrong end index", 7, it.getEndIndex()); + assertEquals("Wrong current index", 0, it.getIndex()); + assertEquals("Wrong current char", 't', it.current()); + assertEquals("Wrong next char", 'e', it.next()); + } + + /** + * @tests java.text.StringCharacterIterator#StringCharacterIterator(java.lang.String, + * int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "StringCharacterIterator", + args = {java.lang.String.class, int.class} + ) + public void test_ConstructorLjava_lang_StringI() { + StringCharacterIterator it = new StringCharacterIterator("testing", 3); + assertEquals("Wrong begin index", 0, it.getBeginIndex()); + assertEquals("Wrong end index", 7, it.getEndIndex()); + assertEquals("Wrong current index", 3, it.getIndex()); + assertEquals("Wrong current char", 't', it.current()); + assertEquals("Wrong next char", 'i', it.next()); + } + + /** + * @tests java.text.StringCharacterIterator#StringCharacterIterator(java.lang.String, + * int, int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "StringCharacterIterator", + args = {java.lang.String.class, int.class, int.class, int.class} + ) + public void test_ConstructorLjava_lang_StringIII() { + StringCharacterIterator it = new StringCharacterIterator("testing", 2, + 6, 4); + assertEquals("Wrong begin index", 2, it.getBeginIndex()); + assertEquals("Wrong end index", 6, it.getEndIndex()); + assertEquals("Wrong current index", 4, it.getIndex()); + assertEquals("Wrong current char", 'i', it.current()); + assertEquals("Wrong next char", 'n', it.next()); + } + + /** + * @tests java.text.StringCharacterIterator#getIndex() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getIndex", + args = {} + ) + public void test_getIndex() { + StringCharacterIterator it1 = new StringCharacterIterator("testing", 2, + 6, 4); + assertEquals("Wrong index 4", 4, it1.getIndex()); + it1.next(); + assertEquals("Wrong index 5", 5, it1.getIndex()); + it1.last(); + assertEquals("Wrong index 4/2", 5, it1.getIndex()); + } + + /** + * @tests java.text.StringCharacterIterator#hashCode() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + StringCharacterIterator it1 = new StringCharacterIterator("testing", 2, + 6, 4); + StringCharacterIterator it2 = new StringCharacterIterator("xxstinx", 2, + 6, 4); + assertTrue("Hash is equal", it1.hashCode() != it2.hashCode()); + StringCharacterIterator it3 = new StringCharacterIterator("testing", 2, + 6, 2); + assertTrue("Hash equal1", it1.hashCode() != it3.hashCode()); + it3 = new StringCharacterIterator("testing", 0, 6, 4); + assertTrue("Hash equal2", it1.hashCode() != it3.hashCode()); + it3 = new StringCharacterIterator("testing", 2, 5, 4); + assertTrue("Hash equal3", it1.hashCode() != it3.hashCode()); + it3 = new StringCharacterIterator("froging", 2, 6, 4); + assertTrue("Hash equal4", it1.hashCode() != it3.hashCode()); + + StringCharacterIterator sci0 = new StringCharacterIterator("fixture"); + assertEquals(sci0.hashCode(), sci0.hashCode()); + + StringCharacterIterator sci1 = new StringCharacterIterator("fixture"); + assertEquals(sci0.hashCode(), sci1.hashCode()); + + sci1.next(); + sci0.next(); + assertEquals(sci0.hashCode(), sci1.hashCode()); + } + + /** + * @tests java.text.StringCharacterIterator#last() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "last", + args = {} + ) + public void test_last() { + StringCharacterIterator it1 = new StringCharacterIterator("testing", 2, + 6, 3); + assertEquals("Wrong last char", 'n', it1.last()); + assertEquals("Wrong previous char", 'i', it1.previous()); + it1 = new StringCharacterIterator("testing", 2, 2, 2); + assertTrue("Not DONE", it1.last() == CharacterIterator.DONE); + } + + /** + * @tests java.text.StringCharacterIterator#setIndex(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setIndex", + args = {int.class} + ) + public void test_setIndexI() { + StringCharacterIterator it1 = new StringCharacterIterator("testing", 2, + 6, 4); + assertEquals("Wrong result1", 's', it1.setIndex(2)); + char result = it1.next(); + assertTrue("Wrong next char: " + result, result == 't'); + assertTrue("Wrong result2", it1.setIndex(6) == CharacterIterator.DONE); + assertEquals("Wrong previous char", 'n', it1.previous()); + } + + /** + * @tests java.text.StringCharacterIterator#setText(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setText", + args = {java.lang.String.class} + ) + public void test_setTextLjava_lang_String() { + StringCharacterIterator it1 = new StringCharacterIterator("testing", 2, + 6, 4); + it1.setText("frog"); + assertEquals("Wrong begin index", 0, it1.getBeginIndex()); + assertEquals("Wrong end index", 4, it1.getEndIndex()); + assertEquals("Wrong current index", 0, it1.getIndex()); + } +} diff --git a/text/src/test/java/tests/text/AllTests.java b/text/src/test/java/tests/text/AllTests.java new file mode 100644 index 0000000..a3eb66d --- /dev/null +++ b/text/src/test/java/tests/text/AllTests.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests.text; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Test suite that includes all tests for the Text project. + * + */ +public class AllTests { + + public static void main(String[] args) { + junit.textui.TestRunner.run(AllTests.suite()); + } + + public static Test suite() { + TestSuite suite = tests.TestSuiteFactory.createTestSuite("All Text test suites"); + // $JUnit-BEGIN$ + suite.addTest(org.apache.harmony.text.tests.java.text.AllTests.suite()); + // $JUnit-END$ + return suite; + } +} diff --git a/text/src/test/resources/serialization/java/text/DecimalFormat.ser b/text/src/test/resources/serialization/java/text/DecimalFormat.ser Binary files differnew file mode 100644 index 0000000..c20fa78 --- /dev/null +++ b/text/src/test/resources/serialization/java/text/DecimalFormat.ser diff --git a/text/src/test/resources/serialization/java/text/DecimalFormatSymbols.ser b/text/src/test/resources/serialization/java/text/DecimalFormatSymbols.ser Binary files differnew file mode 100644 index 0000000..6e086af --- /dev/null +++ b/text/src/test/resources/serialization/java/text/DecimalFormatSymbols.ser |