diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/Annotation.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/Annotation.java new file mode 100644 index 000000000..98013ad47 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/Annotation.java @@ -0,0 +1,35 @@ +/* + * 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.teavm.classlib.java.text; + +public class Annotation { + private Object value; + + public Annotation(Object attribute) { + value = attribute; + } + + public Object getValue() { + return value; + } + + @Override + public String toString() { + return getClass().getName() + "[value=" + value + ']'; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/AttributedCharacterIterator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/AttributedCharacterIterator.java new file mode 100644 index 000000000..6c6096e9c --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/AttributedCharacterIterator.java @@ -0,0 +1,144 @@ +/* + * 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.teavm.classlib.java.text; + +import org.teavm.classlib.java.io.TSerializable; +import org.teavm.classlib.java.util.TMap; +import org.teavm.classlib.java.util.TSet; + +public interface AttributedCharacterIterator extends CharacterIterator { + + public static class Attribute implements TSerializable { + public static final Attribute INPUT_METHOD_SEGMENT = new Attribute( + "input_method_segment"); + + public static final Attribute LANGUAGE = new Attribute("language"); + + public static final Attribute READING = new Attribute("reading"); + + private String name; + + protected Attribute(String name) { + this.name = name; + } + + @Override + public final boolean equals(Object object) { + return this == object; + } + + protected String getName() { + return name; + } + + @Override + public final int hashCode() { + return super.hashCode(); + } + + @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. + */ + public TSet 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. + */ + 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. + */ + public TMap 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. + */ + 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. + */ + 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. + */ + public int getRunLimit(TSet 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. + */ + 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. + */ + 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. + */ + public int getRunStart(TSet attributes); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/AttributedString.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/AttributedString.java new file mode 100644 index 000000000..0ad14c1e6 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/AttributedString.java @@ -0,0 +1,630 @@ +/* + * 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.teavm.classlib.java.text; + +import org.teavm.classlib.java.text.AttributedCharacterIterator.Attribute; +import org.teavm.classlib.java.util.*; + +public class AttributedString { + + String text; + + TMap> 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 THashSet 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) { + THashSet set = new THashSet<>((attributes.length * 4 / 3) + 1); + for (int i = attributes.length; --i >= 0;) { + set.add(attributes[i]); + } + attributesAllowed = set; + } + } + + @Override + @SuppressWarnings("unchecked") + public Object clone() { + try { + AttributedIterator clone = (AttributedIterator) super.clone(); + if (attributesAllowed != null) { + clone.attributesAllowed = (THashSet) attributesAllowed.clone(); + } + return clone; + } catch (CloneNotSupportedException e) { + return null; + } + } + + @Override + public char current() { + if (offset == end) { + return DONE; + } + return attrString.text.charAt(offset); + } + + @Override + public char first() { + if (begin == end) { + return DONE; + } + offset = begin; + return attrString.text.charAt(offset); + } + + @Override + public int getBeginIndex() { + return begin; + } + + @Override + public int getEndIndex() { + return end; + } + + @Override + 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(TList ranges) { + TIterator 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; + } + + @Override + public TSet getAllAttributeKeys() { + if (begin == 0 && end == attrString.text.length() && attributesAllowed == null) { + return attrString.attributeMap.keySet(); + } + + TSet result = new THashSet<>((attrString.attributeMap.size() * 4 / 3) + 1); + TIterator>> it = attrString.attributeMap.entrySet().iterator(); + while (it.hasNext()) { + TMap.Entry> entry = it.next(); + if (attributesAllowed == null || attributesAllowed.contains(entry.getKey())) { + TList ranges = entry.getValue(); + if (inRange(ranges)) { + result.add(entry.getKey()); + } + } + } + return result; + } + + private Object currentValue(TList ranges) { + TIterator 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; + } + + @Override + public Object getAttribute(AttributedCharacterIterator.Attribute attribute) { + if (attributesAllowed != null && !attributesAllowed.contains(attribute)) { + return null; + } + TArrayList ranges = (TArrayList) attrString.attributeMap.get(attribute); + if (ranges == null) { + return null; + } + return currentValue(ranges); + } + + @Override + public TMap getAttributes() { + TMap result = new THashMap<>((attrString.attributeMap.size() * 4 / 3) + 1); + TIterator>> it = attrString.attributeMap.entrySet().iterator(); + while (it.hasNext()) { + TMap.Entry> 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; + } + + @Override + public int getRunLimit() { + return getRunLimit(getAllAttributeKeys()); + } + + private int runLimit(TList ranges) { + int result = end; + TListIterator 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; + } + + @Override + public int getRunLimit(AttributedCharacterIterator.Attribute attribute) { + if (attributesAllowed != null && !attributesAllowed.contains(attribute)) { + return end; + } + TArrayList ranges = (TArrayList) attrString.attributeMap.get(attribute); + if (ranges == null) { + return end; + } + return runLimit(ranges); + } + + @Override + public int getRunLimit(TSet attributes) { + int limit = end; + TIterator it = attributes.iterator(); + while (it.hasNext()) { + AttributedCharacterIterator.Attribute attribute = it.next(); + int newLimit = getRunLimit(attribute); + if (newLimit < limit) { + limit = newLimit; + } + } + return limit; + } + + @Override + public int getRunStart() { + return getRunStart(getAllAttributeKeys()); + } + + private int runStart(TList ranges) { + int result = begin; + TIterator 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; + } + + @Override + public int getRunStart(AttributedCharacterIterator.Attribute attribute) { + if (attributesAllowed != null && !attributesAllowed.contains(attribute)) { + return begin; + } + TArrayList ranges = (TArrayList) attrString.attributeMap.get(attribute); + if (ranges == null) { + return begin; + } + return runStart(ranges); + } + + @Override + public int getRunStart(TSet attributes) { + int start = begin; + TIterator it = attributes.iterator(); + while (it.hasNext()) { + AttributedCharacterIterator.Attribute attribute = it.next(); + int newStart = getRunStart(attribute); + if (newStart > start) { + start = newStart; + } + } + return start; + } + + @Override + public char last() { + if (begin == end) { + return DONE; + } + offset = end - 1; + return attrString.text.charAt(offset); + } + + @Override + public char next() { + if (offset >= (end - 1)) { + offset = end; + return DONE; + } + return attrString.text.charAt(++offset); + } + + @Override + public char previous() { + if (offset == begin) { + return DONE; + } + return attrString.text.charAt(--offset); + } + + @Override + 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); + } + } + + public AttributedString(AttributedCharacterIterator iterator) { + if (iterator.getBeginIndex() > iterator.getEndIndex()) { + throw new IllegalArgumentException("Invalid substring range"); + } + StringBuilder buffer = new StringBuilder(); + for (int i = iterator.getBeginIndex(); i < iterator.getEndIndex(); i++) { + buffer.append(iterator.current()); + iterator.next(); + } + text = buffer.toString(); + TSet attributes = iterator.getAllAttributeKeys(); + if (attributes == null) { + return; + } + attributeMap = new THashMap<>((attributes.size() * 4 / 3) + 1); + + TIterator 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, TSet attributes) { + if (start < iterator.getBeginIndex() || end > iterator.getEndIndex() || start > end) { + throw new IllegalArgumentException(); + } + + if (attributes == null) { + return; + } + + StringBuilder buffer = new StringBuilder(); + iterator.setIndex(start); + while (iterator.getIndex() < end) { + buffer.append(iterator.current()); + iterator.next(); + } + text = buffer.toString(); + attributeMap = new THashMap<>((attributes.size() * 4 / 3) + 1); + + TIterator 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); + } + } + } + + public AttributedString(AttributedCharacterIterator iterator, int start, int end) { + this(iterator, start, end, iterator.getAllAttributeKeys()); + } + + public AttributedString(AttributedCharacterIterator iterator, int start, int end, + AttributedCharacterIterator.Attribute[] attributes) { + this(iterator, start, end, new THashSet<>(TArrays.asList(attributes))); + } + + public AttributedString(String value) { + if (value == null) { + throw new NullPointerException(); + } + text = value; + attributeMap = new THashMap<>(11); + } + + public AttributedString(String value, TMap attributes) { + if (value == null) { + throw new NullPointerException(); + } + if (value.length() == 0 && !attributes.isEmpty()) { + throw new IllegalArgumentException("Cannot add attributes to empty string"); + } + text = value; + attributeMap = new THashMap<>((attributes.size() * 4 / 3) + 1); + TIterator it = attributes.entrySet().iterator(); + while (it.hasNext()) { + TMap.Entry entry = (TMap.Entry) it.next(); + TArrayList ranges = new TArrayList<>(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}. + */ + public void addAttribute(AttributedCharacterIterator.Attribute attribute, Object value) { + if (null == attribute) { + throw new NullPointerException(); + } + if (text.length() == 0) { + throw new IllegalArgumentException(); + } + + TList ranges = attributeMap.get(attribute); + if (ranges == null) { + ranges = new TArrayList(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}. + */ + 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; + } + + TList ranges = attributeMap.get(attribute); + if (ranges == null) { + ranges = new TArrayList<>(1); + ranges.add(new Range(start, end, value)); + attributeMap.put(attribute, ranges); + return; + } + TListIterator it = ranges.listIterator(); + 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; + } + } + 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}. + */ + public void addAttributes(TMap attributes, int start, int end) { + TIterator it = attributes.entrySet().iterator(); + while (it.hasNext()) { + TMap.Entry entry = (TMap.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}. + */ + 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}. + */ + 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}. + */ + public AttributedCharacterIterator getIterator(AttributedCharacterIterator.Attribute[] attributes, int start, + int end) { + return new AttributedIterator(this, attributes, start, end); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/CharacterIterator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/CharacterIterator.java new file mode 100644 index 000000000..c3f0e6434 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/CharacterIterator.java @@ -0,0 +1,42 @@ +/* + * 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.teavm.classlib.java.text; + +public interface CharacterIterator extends Cloneable { + public static final char DONE = '\uffff'; + + public Object clone(); + + public char current(); + + public char first(); + + public int getBeginIndex(); + + public int getEndIndex(); + + public int getIndex(); + + public char last(); + + public char next(); + + public char previous(); + + public char setIndex(int location); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DateFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DateFormat.java new file mode 100644 index 000000000..a6c2851ae --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DateFormat.java @@ -0,0 +1,280 @@ +/* + * 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.teavm.classlib.java.text; + +import org.teavm.classlib.java.util.*; + +public abstract class DateFormat extends Format { + protected Calendar calendar; + protected NumberFormat numberFormat; + public final static int DEFAULT = 2; + public final static int FULL = 0; + public final static int LONG = 1; + public final static int MEDIUM = 2; + public final static int SHORT = 3; + public final static int ERA_FIELD = 0; + public final static int YEAR_FIELD = 1; + public final static int MONTH_FIELD = 2; + public final static int DATE_FIELD = 3; + public final static int HOUR_OF_DAY1_FIELD = 4; + public final static int HOUR_OF_DAY0_FIELD = 5; + public final static int MINUTE_FIELD = 6; + public final static int SECOND_FIELD = 7; + public final static int MILLISECOND_FIELD = 8; + public final static int DAY_OF_WEEK_FIELD = 9; + public final static int DAY_OF_YEAR_FIELD = 10; + public final static int DAY_OF_WEEK_IN_MONTH_FIELD = 11; + public final static int WEEK_OF_YEAR_FIELD = 12; + public final static int WEEK_OF_MONTH_FIELD = 13; + public final static int AM_PM_FIELD = 14; + public final static int HOUR1_FIELD = 15; + public final static int HOUR0_FIELD = 16; + public final static int TIMEZONE_FIELD = 17; + + protected DateFormat() { + } + + @Override + public Object clone() { + DateFormat clone = (DateFormat) super.clone(); + clone.calendar = (Calendar) calendar.clone(); + clone.numberFormat = (NumberFormat) numberFormat.clone(); + return clone; + } + + @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(); + } + + @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(); + } + + public final String format(Date date) { + return format(date, new StringBuffer(), new FieldPosition(0)).toString(); + } + + public abstract StringBuffer format(Date date, StringBuffer buffer, FieldPosition field); + + public static TLocale[] getAvailableLocales() { + return TLocale.getAvailableLocales(); + } + + public Calendar getCalendar() { + return calendar; + } + + public final static DateFormat getDateInstance() { + return getDateInstance(DEFAULT); + } + + public final static DateFormat getDateInstance(int style) { + checkDateStyle(style); + return getDateInstance(style, TLocale.getDefault()); + } + + public final static DateFormat getDateInstance(int style, TLocale locale) { + checkDateStyle(style); + com.ibm.icu.text.DateFormat icuFormat = com.ibm.icu.text.DateFormat.getDateInstance(style, locale); + return new SimpleDateFormat(locale, (com.ibm.icu.text.SimpleDateFormat) icuFormat); + } + + public final static DateFormat getDateTimeInstance() { + return getDateTimeInstance(DEFAULT, DEFAULT); + } + + public final static DateFormat getDateTimeInstance(int dateStyle, int timeStyle) { + checkTimeStyle(timeStyle); + checkDateStyle(dateStyle); + return getDateTimeInstance(dateStyle, timeStyle, TLocale.getDefault()); + } + + public final static DateFormat getDateTimeInstance(int dateStyle, int timeStyle, TLocale locale) { + checkTimeStyle(timeStyle); + checkDateStyle(dateStyle); + com.ibm.icu.text.DateFormat icuFormat = com.ibm.icu.text.DateFormat.getDateTimeInstance(dateStyle, timeStyle, + locale); + return new SimpleDateFormat(locale, (com.ibm.icu.text.SimpleDateFormat) icuFormat); + } + + public final static DateFormat getInstance() { + return getDateTimeInstance(SHORT, SHORT); + } + + public NumberFormat getNumberFormat() { + return numberFormat; + } + + static String getStyleName(int style) { + String styleName; + switch (style) { + case SHORT: + styleName = "SHORT"; + break; + case MEDIUM: + styleName = "MEDIUM"; + break; + case LONG: + styleName = "LONG"; + break; + case FULL: + styleName = "FULL"; + break; + default: + styleName = ""; + } + return styleName; + } + + public final static DateFormat getTimeInstance() { + return getTimeInstance(DEFAULT); + } + + public final static DateFormat getTimeInstance(int style) { + checkTimeStyle(style); + return getTimeInstance(style, TLocale.getDefault()); + } + + public final static DateFormat getTimeInstance(int style, TLocale locale) { + checkTimeStyle(style); + com.ibm.icu.text.DateFormat icuFormat = com.ibm.icu.text.DateFormat.getTimeInstance(style, locale); + return new SimpleDateFormat(locale, (com.ibm.icu.text.SimpleDateFormat) icuFormat); + } + + public TimeZone getTimeZone() { + return calendar.getTimeZone(); + } + + @Override + public int hashCode() { + return calendar.getFirstDayOfWeek() + calendar.getMinimalDaysInFirstWeek() + calendar.getTimeZone().hashCode() + + (calendar.isLenient() ? 1231 : 1237) + numberFormat.hashCode(); + } + + public boolean isLenient() { + return calendar.isLenient(); + } + + public Date parse(String string) throws ParseException { + ParsePosition position = new ParsePosition(0); + Date date = parse(string, position); + if (position.getIndex() == 0) { + throw new ParseException("Unparseable date" + string, position.getErrorIndex()); + } + return date; + } + + public abstract Date parse(String string, ParsePosition position); + + @Override + public Object parseObject(String string, ParsePosition position) { + return parse(string, position); + } + + public void setCalendar(Calendar cal) { + calendar = cal; + } + + public void setLenient(boolean value) { + calendar.setLenient(value); + } + + public void setNumberFormat(NumberFormat format) { + numberFormat = format; + } + + public void setTimeZone(TimeZone timezone) { + calendar.setTimeZone(timezone); + } + + public static class Field extends Format.Field { + private static THashMap table = new THashMap<>(); + public final static Field ERA = new Field("era", Calendar.ERA); + public final static Field YEAR = new Field("year", Calendar.YEAR); + public final static Field MONTH = new Field("month", Calendar.MONTH); + public final static Field HOUR_OF_DAY0 = new Field("hour of day", Calendar.HOUR_OF_DAY); + public final static Field HOUR_OF_DAY1 = new Field("hour of day 1", -1); + public final static Field MINUTE = new Field("minute", Calendar.MINUTE); + public final static Field SECOND = new Field("second", Calendar.SECOND); + public final static Field MILLISECOND = new Field("millisecond", Calendar.MILLISECOND); + public final static Field DAY_OF_WEEK = new Field("day of week", Calendar.DAY_OF_WEEK); + public final static Field DAY_OF_MONTH = new Field("day of month", Calendar.DAY_OF_MONTH); + public final static Field DAY_OF_YEAR = new Field("day of year", Calendar.DAY_OF_YEAR); + public final static Field DAY_OF_WEEK_IN_MONTH = new Field("day of week in month", + Calendar.DAY_OF_WEEK_IN_MONTH); + public final static Field WEEK_OF_YEAR = new Field("week of year", Calendar.WEEK_OF_YEAR); + public final static Field WEEK_OF_MONTH = new Field("week of month", Calendar.WEEK_OF_MONTH); + public final static Field AM_PM = new Field("am pm", Calendar.AM_PM); + public final static Field HOUR0 = new Field("hour", Calendar.HOUR); + public final static Field HOUR1 = new Field("hour 1", -1); + public final static Field TIME_ZONE = new Field("time zone", -1); + private int calendarField = -1; + + 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); + } + } + + public int getCalendarField() { + return calendarField; + } + + public static Field ofCalendarField(int calendarField) { + if (calendarField < 0 || calendarField >= Calendar.FIELD_COUNT) { + throw new IllegalArgumentException(); + } + + return table.get(new Integer(calendarField)); + } + } + + private static void checkDateStyle(int style) { + if (!(style == SHORT || style == MEDIUM || style == LONG || style == FULL || style == DEFAULT)) { + throw new IllegalArgumentException("Illegal date style: " + style); + } + } + + 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("Illegal time style: " + style); + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DateFormatSymbols.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DateFormatSymbols.java new file mode 100644 index 000000000..b28f92306 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DateFormatSymbols.java @@ -0,0 +1,240 @@ +/* + * 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.teavm.classlib.java.text; + +import java.util.Arrays; +import org.teavm.classlib.java.io.TSerializable; +import org.teavm.classlib.java.util.TLocale; + +public class DateFormatSymbols implements TSerializable, Cloneable { + + private static final long serialVersionUID = -5987973545549424702L; + + private String localPatternChars; + + String[] ampms, eras, months, shortMonths, shortWeekdays, weekdays; + + String[][] zoneStrings; + + transient private com.ibm.icu.text.DateFormatSymbols icuSymbols; + + public DateFormatSymbols() { + this(TLocale.getDefault()); + } + + public DateFormatSymbols(TLocale locale) { + this(locale, new com.ibm.icu.text.DateFormatSymbols(locale)); + } + + DateFormatSymbols(TLocale locale, com.ibm.icu.text.DateFormatSymbols icuSymbols) { + this.icuSymbols = icuSymbols; + localPatternChars = icuSymbols.getLocalPatternChars(); + ampms = icuSymbols.getAmPmStrings(); + eras = icuSymbols.getEras(); + months = icuSymbols.getMonths(); + shortMonths = icuSymbols.getShortMonths(); + shortWeekdays = icuSymbols.getShortWeekdays(); + weekdays = icuSymbols.getWeekdays(); + } + + @Override + public Object clone() { + if (zoneStrings == null) { + zoneStrings = icuSymbols.getZoneStrings(); + } + try { + DateFormatSymbols symbols = (DateFormatSymbols) super.clone(); + symbols.ampms = ampms.clone(); + symbols.eras = eras.clone(); + symbols.months = months.clone(); + symbols.shortMonths = shortMonths.clone(); + symbols.shortWeekdays = shortWeekdays.clone(); + symbols.weekdays = weekdays.clone(); + symbols.zoneStrings = new String[zoneStrings.length][]; + for (int i = 0; i < zoneStrings.length; i++) { + symbols.zoneStrings[i] = zoneStrings[i].clone(); + } + return symbols; + } catch (CloneNotSupportedException e) { + return null; + } + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof DateFormatSymbols)) { + return false; + } + if (zoneStrings == null) { + zoneStrings = icuSymbols.getZoneStrings(); + } + + DateFormatSymbols obj = (DateFormatSymbols) object; + + if (obj.zoneStrings == null) { + obj.zoneStrings = obj.icuSymbols.getZoneStrings(); + } + 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; + } + 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; + } + + public String[] getAmPmStrings() { + return ampms.clone(); + } + + public String[] getEras() { + return eras.clone(); + } + + public String getLocalPatternChars() { + return localPatternChars; + } + + public String[] getMonths() { + return months.clone(); + } + + public String[] getShortMonths() { + return shortMonths.clone(); + } + + public String[] getShortWeekdays() { + return shortWeekdays.clone(); + } + + public String[] getWeekdays() { + return weekdays.clone(); + } + + public String[][] getZoneStrings() { + if (zoneStrings == null) { + zoneStrings = icuSymbols.getZoneStrings(); + } + String[][] clone = new String[zoneStrings.length][]; + for (int i = zoneStrings.length; --i >= 0;) { + clone[i] = zoneStrings[i].clone(); + } + return clone; + } + + @Override + public int hashCode() { + if (zoneStrings == null) { + zoneStrings = icuSymbols.getZoneStrings(); + } + 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(); + } + for (String[] element : zoneStrings) { + for (int j = 0; j < element.length; j++) { + if (element[j] != null) { + hashCode += element[j].hashCode(); + } + } + } + return hashCode; + } + + public void setAmPmStrings(String[] data) { + ampms = data.clone(); + } + + public void setEras(String[] data) { + eras = data.clone(); + } + + public void setLocalPatternChars(String data) { + if (data == null) { + throw new NullPointerException(); + } + localPatternChars = data; + } + + public void setMonths(String[] data) { + months = data.clone(); + } + + public void setShortMonths(String[] data) { + shortMonths = data.clone(); + } + + public void setShortWeekdays(String[] data) { + shortWeekdays = data.clone(); + } + + public void setWeekdays(String[] data) { + weekdays = data.clone(); + } + + public void setZoneStrings(String[][] data) { + zoneStrings = data.clone(); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DecimalFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DecimalFormat.java new file mode 100644 index 000000000..dbba979dd --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DecimalFormat.java @@ -0,0 +1,432 @@ +/* + * 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.teavm.classlib.java.text; + +import org.teavm.classlib.java.util.TLocale; + + +public class DecimalFormat extends NumberFormat { + + private static final long serialVersionUID = 864413376551465018L; + + private transient boolean parseBigDecimal = false; + + private transient DecimalFormatSymbols symbols; + + private transient com.ibm.icu.text.DecimalFormat dform; + + private transient com.ibm.icu.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. + */ + public DecimalFormat() { + TLocale locale = TLocale.getDefault(); + icuSymbols = new com.ibm.icu.text.DecimalFormatSymbols(locale); + symbols = new DecimalFormatSymbols(locale); + dform = new com.ibm.icu.text.DecimalFormat(); + + super.setMaximumFractionDigits(dform.getMaximumFractionDigits()); + super.setMaximumIntegerDigits(dform.getMaximumIntegerDigits()); + super.setMinimumFractionDigits(dform.getMinimumFractionDigits()); + super.setMinimumIntegerDigits(dform.getMinimumIntegerDigits()); + } + + /** + * 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. + * @throws IllegalArgumentException + * if the pattern cannot be parsed. + */ + public DecimalFormat(String pattern) { + TLocale locale = TLocale.getDefault(); + icuSymbols = new com.ibm.icu.text.DecimalFormatSymbols(locale); + symbols = new DecimalFormatSymbols(locale); + dform = new com.ibm.icu.text.DecimalFormat(pattern, icuSymbols); + + super.setMaximumFractionDigits(dform.getMaximumFractionDigits()); + super.setMaximumIntegerDigits(dform.getMaximumIntegerDigits()); + super.setMinimumFractionDigits(dform.getMinimumFractionDigits()); + super.setMinimumIntegerDigits(dform.getMinimumIntegerDigits()); + } + + /** + * Constructs a new {@code DecimalFormat} using the specified non-localized + * pattern and {@code DecimalFormatSymbols}. + * + * @param pattern + * the non-localized pattern. + * @param value + * the DecimalFormatSymbols. + * @throws IllegalArgumentException + * if the pattern cannot be parsed. + */ + public DecimalFormat(String pattern, DecimalFormatSymbols value) { + symbols = (DecimalFormatSymbols) value.clone(); + TLocale locale = symbols.getLocale(); + icuSymbols = new com.ibm.icu.text.DecimalFormatSymbols(locale); + copySymbols(icuSymbols, symbols); + + dform = new com.ibm.icu.text.DecimalFormat(pattern, icuSymbols); + + super.setMaximumFractionDigits(dform.getMaximumFractionDigits()); + super.setMaximumIntegerDigits(dform.getMaximumIntegerDigits()); + super.setMinimumFractionDigits(dform.getMinimumFractionDigits()); + super.setMinimumIntegerDigits(dform.getMinimumIntegerDigits()); + } + + DecimalFormat(String pattern, DecimalFormatSymbols value, com.ibm.icu.text.DecimalFormat icuFormat) { + symbols = value; + icuSymbols = value.getIcuSymbols(); + dform = icuFormat; + + 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. + * @throws IllegalArgumentException + * if the pattern cannot be parsed. + */ + public void applyLocalizedPattern(String pattern) { + dform.applyLocalizedPattern(pattern); + 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 non-localized pattern characters. + * + * @param pattern + * the non-localized pattern. + * @throws IllegalArgumentException + * if the pattern cannot be parsed. + */ + public void applyPattern(String pattern) { + + dform.applyPattern(pattern); + super.setMaximumFractionDigits(dform.getMaximumFractionDigits()); + super.setMaximumIntegerDigits(dform.getMaximumIntegerDigits()); + super.setMinimumFractionDigits(dform.getMinimumFractionDigits()); + super.setMinimumIntegerDigits(dform.getMinimumIntegerDigits()); + } + + /** + * 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 + */ + @Override + public Object clone() { + DecimalFormat clone = (DecimalFormat) super.clone(); + clone.dform = (com.ibm.icu.text.DecimalFormat) dform.clone(); + clone.symbols = (DecimalFormatSymbols) symbols.clone(); + return clone; + } + + @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)); + } + + @Override + public AttributedCharacterIterator formatToCharacterIterator(Object object) { + if (object == null) { + throw new NullPointerException(); + } + return dform.formatToCharacterIterator(object); + } + + @Override + public StringBuffer format(double value, StringBuffer buffer, + FieldPosition position) { + return dform.format(value, buffer, position); + } + + @Override + public StringBuffer format(long value, StringBuffer buffer, + FieldPosition position) { + return dform.format(value, buffer, position); + } + + @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); + } + + public DecimalFormatSymbols getDecimalFormatSymbols() { + return (DecimalFormatSymbols) symbols.clone(); + } + + @Override + public Currency getCurrency() { + final com.ibm.icu.util.Currency cur = dform.getCurrency(); + final String code = (cur == null) ? "XXX" : cur.getCurrencyCode(); //$NON-NLS-1$ + + return Currency.getInstance(code); + } + + public int getGroupingSize() { + return dform.getGroupingSize(); + } + + public int getMultiplier() { + return dform.getMultiplier(); + } + + public String getNegativePrefix() { + return dform.getNegativePrefix(); + } + + public String getNegativeSuffix() { + return dform.getNegativeSuffix(); + } + + public String getPositivePrefix() { + return dform.getPositivePrefix(); + } + + public String getPositiveSuffix() { + return dform.getPositiveSuffix(); + } + + @Override + public int hashCode() { + return dform.hashCode(); + } + + public boolean isDecimalSeparatorAlwaysShown() { + return dform.isDecimalSeparatorAlwaysShown(); + } + + public boolean isParseBigDecimal() { + return this.parseBigDecimal; + } + + @Override + public void setParseIntegerOnly(boolean value) { + // In this implementation, com.ibm.icu.text.DecimalFormat is wrapped to + // fulfill most of the format and parse feature. And this method is + // delegated to the wrapped instance of com.ibm.icu.text.DecimalFormat. + + dform.setParseIntegerOnly(value); + } + + @Override + public boolean isParseIntegerOnly() { + return dform.isParseIntegerOnly(); + } + + private static final Double NEGATIVE_ZERO_DOUBLE = new Double(-0.0); + + @Override + public Number parse(String string, ParsePosition position) { + Number number = dform.parse(string, position); + if (null == number) { + return null; + } + 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()); + } + + if (this.isParseIntegerOnly() && number.equals(NEGATIVE_ZERO_DOUBLE)) { + return new Long(0); + } + return number; + + } + + public void setDecimalFormatSymbols(DecimalFormatSymbols value) { + if (value != null) { + symbols = (DecimalFormatSymbols) value.clone(); + icuSymbols = dform.getDecimalFormatSymbols(); + copySymbols(icuSymbols, symbols); + dform.setDecimalFormatSymbols(icuSymbols); + } + } + + @Override + public void setCurrency(Currency currency) { + dform.setCurrency(com.ibm.icu.util.Currency.getInstance(currency + .getCurrencyCode())); + symbols.setCurrency(currency); + } + + public void setDecimalSeparatorAlwaysShown(boolean value) { + dform.setDecimalSeparatorAlwaysShown(value); + } + + public void setGroupingSize(int value) { + dform.setGroupingSize(value); + } + + @Override + public void setGroupingUsed(boolean value) { + dform.setGroupingUsed(value); + } + + @Override + public boolean isGroupingUsed() { + return dform.isGroupingUsed(); + } + + @Override + public void setMaximumFractionDigits(int value) { + super.setMaximumFractionDigits(value); + dform.setMaximumFractionDigits(value); + } + + @Override + public void setMaximumIntegerDigits(int value) { + super.setMaximumIntegerDigits(value); + dform.setMaximumIntegerDigits(value); + } + + @Override + public void setMinimumFractionDigits(int value) { + super.setMinimumFractionDigits(value); + dform.setMinimumFractionDigits(value); + } + + @Override + public void setMinimumIntegerDigits(int value) { + super.setMinimumIntegerDigits(value); + dform.setMinimumIntegerDigits(value); + } + + public void setMultiplier(int value) { + dform.setMultiplier(value); + } + + public void setNegativePrefix(String value) { + dform.setNegativePrefix(value); + } + + public void setNegativeSuffix(String value) { + dform.setNegativeSuffix(value); + } + + public void setPositivePrefix(String value) { + dform.setPositivePrefix(value); + } + + public void setPositiveSuffix(String value) { + dform.setPositiveSuffix(value); + } + + public void setParseBigDecimal(boolean newValue) { + this.parseBigDecimal = newValue; + } + + public String toLocalizedPattern() { + return dform.toLocalizedPattern(); + } + + public String toPattern() { + return dform.toPattern(); + } + + /* + * 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.icu.text.DecimalFormatSymbols icu, + final DecimalFormatSymbols dfs) { + Currency currency = dfs.getCurrency(); + if (currency == null) { + icu.setCurrency(com.ibm.icu.util.Currency.getInstance("XXX")); //$NON-NLS-1$ + } else { + icu.setCurrency(com.ibm.icu.util.Currency.getInstance(dfs + .getCurrency().getCurrencyCode())); + } + + 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()); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DecimalFormatSymbols.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DecimalFormatSymbols.java new file mode 100644 index 000000000..92eec39e9 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DecimalFormatSymbols.java @@ -0,0 +1,280 @@ +/* + * 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.teavm.classlib.java.text; + +import java.util.Arrays; +import org.teavm.classlib.java.io.TSerializable; +import org.teavm.classlib.java.lang.TCloneable; +import org.teavm.classlib.java.util.TLocale; + +public final class DecimalFormatSymbols implements TCloneable, TSerializable { + + 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 TLocale 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()}. + */ + public DecimalFormatSymbols() { + this(TLocale.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. + */ + public DecimalFormatSymbols(TLocale locale) { + this(locale, new com.ibm.icu.text.DecimalFormatSymbols(locale)); + } + + transient private com.ibm.icu.text.DecimalFormatSymbols icuSymbols; + + DecimalFormatSymbols(TLocale locale, com.ibm.icu.text.DecimalFormatSymbols icuSymbols) { + this.icuSymbols = icuSymbols; + infinity = icuSymbols.getInfinity(); + NaN = icuSymbols.getNaN(); + this.locale = locale; + currencySymbol = icuSymbols.getCurrencySymbol(); + intlCurrencySymbol = icuSymbols.getInternationalCurrencySymbol(); + if (locale.getCountry().length() == 0) { + currency = Currency.getInstance("XXX"); //$NON-NLS-1$ + } else { + currency = Currency.getInstance(locale); + } + patternChars = new char[10]; + patternChars[ZeroDigit] = icuSymbols.getZeroDigit(); + patternChars[Digit] = icuSymbols.getDigit(); + patternChars[DecimalSeparator] = icuSymbols.getDecimalSeparator(); + patternChars[GroupingSeparator] = icuSymbols.getGroupingSeparator(); + patternChars[PatternSeparator] = icuSymbols.getPatternSeparator(); + patternChars[Percent] = icuSymbols.getPercent(); + patternChars[PerMill] = icuSymbols.getPerMill(); + patternChars[Exponent] = icuSymbols.getExponentSeparator().charAt(0); + patternChars[MonetaryDecimalSeparator] = icuSymbols + .getMonetaryDecimalSeparator(); + patternChars[MinusSign] = icuSymbols.getMinusSign(); + } + + @Override + public Object clone() { + try { + DecimalFormatSymbols symbols = (DecimalFormatSymbols) super.clone(); + symbols.patternChars = patternChars.clone(); + return symbols; + } catch (CloneNotSupportedException e) { + return null; + } + } + + @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); + } + + public Currency getCurrency() { + return currency; + } + + public String getInternationalCurrencySymbol() { + return intlCurrencySymbol; + } + + public String getCurrencySymbol() { + return currencySymbol; + } + + public char getDecimalSeparator() { + return patternChars[DecimalSeparator]; + } + + public char getDigit() { + return patternChars[Digit]; + } + + public char getGroupingSeparator() { + return patternChars[GroupingSeparator]; + } + + public String getInfinity() { + return infinity; + } + + String getLocalPatternChars() { + // Don't include the MonetaryDecimalSeparator or the MinusSign + return new String(patternChars, 0, patternChars.length - 2); + } + + public char getMinusSign() { + return patternChars[MinusSign]; + } + + public char getMonetaryDecimalSeparator() { + return patternChars[MonetaryDecimalSeparator]; + } + + public String getNaN() { + return NaN; + } + + public char getPatternSeparator() { + return patternChars[PatternSeparator]; + } + + public char getPercent() { + return patternChars[Percent]; + } + + public char getPerMill() { + return patternChars[PerMill]; + } + + public char getZeroDigit() { + return patternChars[ZeroDigit]; + } + + char getExponential() { + return patternChars[Exponent]; + } + + @Override + public int hashCode() { + return new String(patternChars).hashCode() + infinity.hashCode() + + NaN.hashCode() + currencySymbol.hashCode() + + intlCurrencySymbol.hashCode(); + } + + 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); + } + + 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; + } + + public void setCurrencySymbol(String value) { + currencySymbol = value; + } + + public void setDecimalSeparator(char value) { + patternChars[DecimalSeparator] = value; + } + + public void setDigit(char value) { + patternChars[Digit] = value; + } + + public void setGroupingSeparator(char value) { + patternChars[GroupingSeparator] = value; + } + + public void setInfinity(String value) { + infinity = value; + } + + public void setMinusSign(char value) { + patternChars[MinusSign] = value; + } + + public void setMonetaryDecimalSeparator(char value) { + patternChars[MonetaryDecimalSeparator] = value; + } + + public void setNaN(String value) { + NaN = value; + } + + public void setPatternSeparator(char value) { + patternChars[PatternSeparator] = value; + } + + public void setPercent(char value) { + patternChars[Percent] = value; + } + + public void setPerMill(char value) { + patternChars[PerMill] = value; + } + + public void setZeroDigit(char value) { + patternChars[ZeroDigit] = value; + } + + void setExponential(char value) { + patternChars[Exponent] = value; + } + + TLocale getLocale(){ + return locale; + } + + com.ibm.icu.text.DecimalFormatSymbols getIcuSymbols() { + return icuSymbols; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/FieldPosition.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/FieldPosition.java new file mode 100644 index 000000000..de14cc1ba --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/FieldPosition.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.teavm.classlib.java.text; + +public class FieldPosition { + private int myField, beginIndex, endIndex; + private Format.Field myAttribute; + + public FieldPosition(int field) { + myField = field; + } + + public FieldPosition(Format.Field attribute) { + myAttribute = attribute; + myField = -1; + } + + public FieldPosition(Format.Field attribute, int field) { + myAttribute = attribute; + myField = field; + } + + void clear() { + beginIndex = endIndex = 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; + } + + public int getBeginIndex() { + return beginIndex; + } + + public int getEndIndex() { + return endIndex; + } + + public int getField() { + return myField; + } + + public Format.Field getFieldAttribute() { + return myAttribute; + } + + @Override + public int hashCode() { + int attributeHash = (myAttribute == null) ? 0 : myAttribute.hashCode(); + return attributeHash + myField * 10 + beginIndex * 100 + endIndex; + } + + public void setBeginIndex(int index) { + beginIndex = index; + } + + public void setEndIndex(int index) { + endIndex = index; + } + + @Override + public String toString() { + return getClass().getName() + "[attribute=" + myAttribute + ", field=" + myField + ", beginIndex=" + + beginIndex + ", endIndex=" + endIndex + "]"; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/Format.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/Format.java new file mode 100644 index 000000000..9d9f3f093 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/Format.java @@ -0,0 +1,137 @@ +/* + * 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.teavm.classlib.java.text; + +import org.teavm.classlib.java.io.TSerializable; +import org.teavm.classlib.java.lang.TCloneable; + +public abstract class Format implements TSerializable, TCloneable { + public Format() { + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + 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'))) { + throw new IllegalArgumentException("Invalid pattern char" + next + " in " + template); + } else { + output.append(next); + } + } + if (quote) { + throw new IllegalArgumentException("Unterminated quote"); + } + return output.toString(); + } + + public final String format(Object object) { + return format(object, new StringBuffer(), new FieldPosition(0)).toString(); + } + + public abstract StringBuffer format(Object object, StringBuffer buffer, FieldPosition field); + + public AttributedCharacterIterator formatToCharacterIterator(Object object) { + return new AttributedString(format(object)).getIterator(); + } + + public Object parseObject(String string) throws ParseException { + ParsePosition position = new ParsePosition(0); + Object result = parseObject(string, position); + if (position.getIndex() == 0) { + throw new ParseException("Format.parseObject(String) parse failure", position.getErrorIndex()); + } + return result; + } + + 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); + } + throw new IllegalArgumentException("Unmatched braces in the pattern"); + } + + public static class Field extends AttributedCharacterIterator.Attribute { + protected Field(String fieldName) { + super(fieldName); + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/NumberFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/NumberFormat.java new file mode 100644 index 000000000..f061e1202 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/NumberFormat.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 org.teavm.classlib.java.text; + +import org.teavm.classlib.java.util.TLocale; + +public abstract class NumberFormat extends Format { + + private static final long serialVersionUID = -2308460125733713944L; + public static final int INTEGER_FIELD = 0; + public static final int FRACTION_FIELD = 1; + + private boolean groupingUsed = true, parseIntegerOnly = false; + + private int maximumIntegerDigits = 40, minimumIntegerDigits = 1, + maximumFractionDigits = 3, minimumFractionDigits = 0; + + public NumberFormat() { + } + + @Override + public Object clone() { + return super.clone(); + } + + @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; + } + + public final String format(double value) { + return format(value, new StringBuffer(), new FieldPosition(0)) + .toString(); + } + + public abstract StringBuffer format(double value, StringBuffer buffer, + FieldPosition field); + + public final String format(long value) { + return format(value, new StringBuffer(), new FieldPosition(0)) + .toString(); + } + public abstract StringBuffer format(long value, StringBuffer buffer, + FieldPosition field); + + @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(); + } + + public static TLocale[] getAvailableLocales() { + return TLocale.getAvailableLocales(); + } + + public Currency getCurrency() { + throw new UnsupportedOperationException(); + } + + public final static NumberFormat getCurrencyInstance() { + return getCurrencyInstance(TLocale.getDefault()); + } + + public static NumberFormat getCurrencyInstance(TLocale locale) { + com.ibm.icu.text.DecimalFormat icuFormat = (com.ibm.icu.text.DecimalFormat) com.ibm.icu.text.NumberFormat + .getCurrencyInstance(locale); + String pattern = icuFormat.toPattern(); + return new DecimalFormat(pattern, new DecimalFormatSymbols(locale)); + } + + public final static NumberFormat getIntegerInstance() { + return getIntegerInstance(TLocale.getDefault()); + } + + public static NumberFormat getIntegerInstance(TLocale locale) { + com.ibm.icu.text.DecimalFormat icuFormat = (com.ibm.icu.text.DecimalFormat) com.ibm.icu.text.NumberFormat + .getIntegerInstance(locale); + String pattern = icuFormat.toPattern(); + DecimalFormat format = new DecimalFormat(pattern, new DecimalFormatSymbols(locale)); + format.setParseIntegerOnly(true); + return format; + + } + + public final static NumberFormat getInstance() { + return getNumberInstance(); + } + + public static NumberFormat getInstance(TLocale locale) { + return getNumberInstance(locale); + } + + public int getMaximumFractionDigits() { + return maximumFractionDigits; + } + + public int getMaximumIntegerDigits() { + return maximumIntegerDigits; + } + + public int getMinimumFractionDigits() { + return minimumFractionDigits; + } + + public int getMinimumIntegerDigits() { + return minimumIntegerDigits; + } + + public final static NumberFormat getNumberInstance() { + return getNumberInstance(TLocale.getDefault()); + } + + public static NumberFormat getNumberInstance(TLocale locale) { + com.ibm.icu.text.DecimalFormat icuFormat = (com.ibm.icu.text.DecimalFormat) com.ibm.icu.text.NumberFormat + .getNumberInstance(locale); + String pattern = icuFormat.toPattern(); + return new DecimalFormat(pattern, new DecimalFormatSymbols(locale, icuFormat.getDecimalFormatSymbols()), icuFormat); + } + + public final static NumberFormat getPercentInstance() { + return getPercentInstance(TLocale.getDefault()); + } + + public static NumberFormat getPercentInstance(TLocale locale) { + com.ibm.icu.text.DecimalFormat icuFormat = (com.ibm.icu.text.DecimalFormat) com.ibm.icu.text.NumberFormat + .getPercentInstance(locale); + String pattern = icuFormat.toPattern(); + return new DecimalFormat(pattern, new DecimalFormatSymbols(locale)); + } + + @Override + public int hashCode() { + return (groupingUsed ? 1231 : 1237) + (parseIntegerOnly ? 1231 : 1237) + + maximumFractionDigits + maximumIntegerDigits + + minimumFractionDigits + minimumIntegerDigits; + } + + public boolean isGroupingUsed() { + return groupingUsed; + } + + public boolean isParseIntegerOnly() { + return parseIntegerOnly; + } + + public Number parse(String string) throws ParseException { + ParsePosition pos = new ParsePosition(0); + Number number = parse(string, pos); + if (pos.getIndex() == 0) { + throw new ParseException("Unparseable number: " + string, pos.getErrorIndex()); + } + return number; + } + + public abstract Number parse(String string, ParsePosition position); + + @Override + public final Object parseObject(String string, ParsePosition position) { + if (position == null) { + throw new NullPointerException("position is null"); + } + + try { + return parse(string, position); + } catch (Exception e) { + return null; + } + } + + public void setCurrency(Currency currency) { + throw new UnsupportedOperationException(); + } + + public void setGroupingUsed(boolean value) { + groupingUsed = value; + } + + public void setMaximumFractionDigits(int value) { + maximumFractionDigits = value < 0 ? 0 : value; + if (maximumFractionDigits < minimumFractionDigits) { + minimumFractionDigits = maximumFractionDigits; + } + } + + public void setMaximumIntegerDigits(int value) { + maximumIntegerDigits = value < 0 ? 0 : value; + if (maximumIntegerDigits < minimumIntegerDigits) { + minimumIntegerDigits = maximumIntegerDigits; + } + } + + public void setMinimumFractionDigits(int value) { + minimumFractionDigits = value < 0 ? 0 : value; + if (maximumFractionDigits < minimumFractionDigits) { + maximumFractionDigits = minimumFractionDigits; + } + } + + public void setMinimumIntegerDigits(int value) { + minimumIntegerDigits = value < 0 ? 0 : value; + if (maximumIntegerDigits < minimumIntegerDigits) { + maximumIntegerDigits = minimumIntegerDigits; + } + } + + public void setParseIntegerOnly(boolean value) { + parseIntegerOnly = value; + } + + + public static class Field extends Format.Field { + public static final Field SIGN = new Field("sign"); + public static final Field INTEGER = new Field("integer"); + public static final Field FRACTION = new Field("fraction"); + public static final Field EXPONENT = new Field("exponent"); + public static final Field EXPONENT_SIGN = new Field("exponent sign"); + public static final Field EXPONENT_SYMBOL = new Field("exponent symbol"); + public static final Field DECIMAL_SEPARATOR = new Field("decimal separator"); + public static final Field GROUPING_SEPARATOR = new Field("grouping separator"); + public static final Field PERCENT = new Field("percent"); + public static final Field PERMILLE = new Field("per mille"); + public static final Field CURRENCY = new Field("currency"); + + protected Field(String fieldName) { + super(fieldName); + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/ParseException.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/ParseException.java new file mode 100644 index 000000000..fa1c7af84 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/ParseException.java @@ -0,0 +1,51 @@ +/* + * 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.teavm.classlib.java.text; + +/** + * Thrown when the string being parsed is not in the correct form. + */ +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. + */ + 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. + */ + public int getErrorOffset() { + return errorOffset; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/ParsePosition.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/ParsePosition.java new file mode 100644 index 000000000..2c5adb00c --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/ParsePosition.java @@ -0,0 +1,62 @@ +/* + * 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.teavm.classlib.java.text; + +public class ParsePosition { + + private int currentPosition, errorIndex = -1; + + public ParsePosition(int index) { + currentPosition = index; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof ParsePosition)) { + return false; + } + ParsePosition pos = (ParsePosition) object; + return currentPosition == pos.currentPosition && errorIndex == pos.errorIndex; + } + + public int getErrorIndex() { + return errorIndex; + } + + public int getIndex() { + return currentPosition; + } + + @Override + public int hashCode() { + return currentPosition + errorIndex; + } + + public void setErrorIndex(int index) { + errorIndex = index; + } + + public void setIndex(int index) { + currentPosition = index; + } + + @Override + public String toString() { + return getClass().getName() + "[index=" + currentPosition + ", errorIndex=" + errorIndex + "]"; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/SimpleDateFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/SimpleDateFormat.java new file mode 100644 index 000000000..7039c24e9 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/SimpleDateFormat.java @@ -0,0 +1,597 @@ +/* + * 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.teavm.classlib.java.text; + +import org.teavm.classlib.java.util.*; + +public class SimpleDateFormat extends DateFormat { + + private static final long serialVersionUID = 4774881970558875024L; + + private static final String patternChars = "GyMdkHmsSEDFwWahKzYeugAZvcLQqV"; //$NON-NLS-1$ + + private String pattern; + + private DateFormatSymbols formatData; + + transient private int creationYear; + + private Date defaultCenturyStart; + + private transient String tzId; + + private transient com.ibm.icu.text.SimpleDateFormat icuFormat; + + public SimpleDateFormat() { + this(TLocale.getDefault()); + icuFormat = new com.ibm.icu.text.SimpleDateFormat(); + icuFormat.setTimeZone(com.ibm.icu.util.TimeZone.getTimeZone(tzId)); + pattern = (String) getInternalField("pattern", icuFormat); + formatData = new DateFormatSymbols(TLocale.getDefault()); + } + + public SimpleDateFormat(String pattern) { + this(pattern, TLocale.getDefault()); + } + + private void validateFormat(char format) { + int index = patternChars.indexOf(format); + if (index == -1) { + throw new IllegalArgumentException("Unknown pattern character - " + format); + } + } + + 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) { + throw new IllegalArgumentException("Unterminated quote"); + } + + } + + public SimpleDateFormat(String template, DateFormatSymbols value) { + this(TLocale.getDefault()); + validatePattern(template); + icuFormat = new com.ibm.icu.text.SimpleDateFormat(template, Locale.getDefault()); + icuFormat.setTimeZone(com.ibm.icu.util.TimeZone.getTimeZone(tzId)); + pattern = template; + formatData = (DateFormatSymbols) value.clone(); + } + + private void copySymbols(DateFormatSymbols value, com.ibm.icu.text.DateFormatSymbols icuSymbols) { + icuSymbols.setAmPmStrings(value.getAmPmStrings()); + icuSymbols.setEras(value.getEras()); + icuSymbols.setLocalPatternChars(value.getLocalPatternChars()); + icuSymbols.setMonths(value.getMonths()); + icuSymbols.setShortMonths(value.getShortMonths()); + icuSymbols.setShortWeekdays(value.getShortWeekdays()); + icuSymbols.setWeekdays(value.getWeekdays()); + icuSymbols.setZoneStrings(value.getZoneStrings()); + } + + public SimpleDateFormat(String template, TLocale locale) { + this(locale); + validatePattern(template); + icuFormat = new com.ibm.icu.text.SimpleDateFormat(template, locale); + icuFormat.setTimeZone(com.ibm.icu.util.TimeZone.getTimeZone(tzId)); + pattern = template; + formatData = new DateFormatSymbols(locale, icuFormat.getDateFormatSymbols()); + } + + SimpleDateFormat(TLocale locale, com.ibm.icu.text.SimpleDateFormat icuFormat) { + this(locale); + this.icuFormat = icuFormat; + this.icuFormat.setTimeZone(com.ibm.icu.util.TimeZone.getTimeZone(tzId)); + pattern = (String) Format.getInternalField("pattern", icuFormat); + formatData = new DateFormatSymbols(locale); + } + + private SimpleDateFormat(TLocale locale) { + numberFormat = NumberFormat.getInstance(locale); + numberFormat.setParseIntegerOnly(true); + numberFormat.setGroupingUsed(false); + calendar = new GregorianCalendar(locale); + calendar.add(Calendar.YEAR, -80); + tzId = calendar.getTimeZone().getID(); + creationYear = calendar.get(Calendar.YEAR); + defaultCenturyStart = calendar.getTime(); + } + + public void applyLocalizedPattern(String template) { + icuFormat.applyLocalizedPattern(template); + pattern = icuFormat.toPattern(); + } + + public void applyPattern(String template) { + validatePattern(template); + /* + * ICU spec explicitly mentions that "ICU interprets a single 'y' + * differently than Java." We need to do a trick here to follow Java + * spec. + */ + String templateForICU = patternForICU(template); + icuFormat.applyPattern(templateForICU); + pattern = template; + } + + @SuppressWarnings("nls") + private String patternForICU(String p) { + String[] subPatterns = p.split("'"); + boolean quote = false; + boolean first = true; + StringBuilder result = new StringBuilder(); + for (String subPattern : subPatterns) { + if (!quote) { + // replace 'y' with 'yy' for ICU to follow Java spec + result.append((first ? "" : "'") + subPattern.replaceAll("(? fields = new TArrayList<>(); + + // 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.get(i); + Format.Field attribute = pos.getFieldAttribute(); + as.addAttribute(attribute, attribute, pos.getBeginIndex(), pos.getEndIndex()); + } + + // return the CharacterIterator from AttributedString + return as.getIterator(); + } + + private StringBuffer formatImpl(Date date, StringBuffer buffer, FieldPosition field, TList 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; + } + + private void append(StringBuffer buffer, FieldPosition position, TList fields, char format, int count) { + int field = -1; + int index = patternChars.indexOf(format); + if (index == -1) { + throw new IllegalArgumentException("Unknown pattern character - " + format); + } + + 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 com.ibm.icu.text.DateFormat.TIMEZONE_RFC_FIELD: // 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(); + String[][] zones = formatData.getZoneStrings(); + 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"); + 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); + } + + @Override + public StringBuffer format(Date date, StringBuffer buffer, FieldPosition fieldPos) { + String id = calendar.getTimeZone().getID(); + if (!tzId.equals(id)) { + tzId = id; + icuFormat.setTimeZone(com.ibm.icu.util.TimeZone.getTimeZone(tzId)); + } + // As ICU has its own implementation for DateFormat.Field, we need to + // pass an ICU instance of DateFormat.Field to the FieldPosition to get + // the begin and end index. + StringBuffer result = null; + Format.Field attribute = fieldPos.getFieldAttribute(); + if (attribute instanceof DateFormat.Field) { + com.ibm.icu.text.DateFormat.Field icuAttribute = toICUField((DateFormat.Field) attribute); + int field = fieldPos.getField(); + FieldPosition icuFieldPos = new FieldPosition(icuAttribute, field); + result = icuFormat.format(date, buffer, icuFieldPos); + fieldPos.setBeginIndex(icuFieldPos.getBeginIndex()); + fieldPos.setEndIndex(icuFieldPos.getEndIndex()); + return result; + } + return icuFormat.format(date, buffer, fieldPos); + } + + private com.ibm.icu.text.DateFormat.Field toICUField(DateFormat.Field attribute) { + com.ibm.icu.text.DateFormat.Field icuAttribute = null; + + if (attribute == DateFormat.Field.ERA) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.ERA; + } else if (attribute == DateFormat.Field.YEAR) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.YEAR; + } else if (attribute == DateFormat.Field.MONTH) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.MONTH; + } else if (attribute == DateFormat.Field.HOUR_OF_DAY0) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.HOUR_OF_DAY0; + } else if (attribute == DateFormat.Field.HOUR_OF_DAY1) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.HOUR_OF_DAY1; + } else if (attribute == DateFormat.Field.MINUTE) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.MINUTE; + } else if (attribute == DateFormat.Field.SECOND) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.SECOND; + } else if (attribute == DateFormat.Field.MILLISECOND) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.MILLISECOND; + } else if (attribute == DateFormat.Field.DAY_OF_WEEK) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.DAY_OF_WEEK; + } else if (attribute == DateFormat.Field.DAY_OF_MONTH) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.DAY_OF_MONTH; + } else if (attribute == DateFormat.Field.DAY_OF_YEAR) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.DAY_OF_YEAR; + } else if (attribute == DateFormat.Field.DAY_OF_WEEK_IN_MONTH) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.DAY_OF_WEEK_IN_MONTH; + } else if (attribute == DateFormat.Field.WEEK_OF_YEAR) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.WEEK_OF_YEAR; + } else if (attribute == DateFormat.Field.WEEK_OF_MONTH) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.WEEK_OF_MONTH; + } else if (attribute == DateFormat.Field.AM_PM) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.AM_PM; + } else if (attribute == DateFormat.Field.HOUR0) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.HOUR0; + } else if (attribute == DateFormat.Field.HOUR1) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.HOUR1; + } else if (attribute == DateFormat.Field.TIME_ZONE) { + icuAttribute = com.ibm.icu.text.DateFormat.Field.TIME_ZONE; + } + + return icuAttribute; + } + + public Date get2DigitYearStart() { + return defaultCenturyStart; + } + + 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; + } + + @Override + public Date parse(String string, ParsePosition position) { + String id = calendar.getTimeZone().getID(); + if (!tzId.equals(id)) { + tzId = id; + icuFormat.setTimeZone(com.ibm.icu.util.TimeZone.getTimeZone(tzId)); + } + icuFormat.setLenient(calendar.isLenient()); + return icuFormat.parse(string, position); + } + + public void set2DigitYearStart(Date date) { + icuFormat.set2DigitYearStart(date); + defaultCenturyStart = date; + Calendar cal = new GregorianCalendar(); + cal.setTime(date); + creationYear = cal.get(Calendar.YEAR); + } + + public void setDateFormatSymbols(DateFormatSymbols value) { + com.ibm.icu.text.DateFormatSymbols icuSymbols = new com.ibm.icu.text.DateFormatSymbols(); + copySymbols(value, icuSymbols); + icuFormat.setDateFormatSymbols(icuSymbols); + formatData = (DateFormatSymbols) value.clone(); + } + + public String toLocalizedPattern() { + return icuFormat.toLocalizedPattern(); + } + + public String toPattern() { + return pattern; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/Date.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/Date.java index 444558a8a..6c074f2ad 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/Date.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/Date.java @@ -32,12 +32,12 @@ package org.teavm.classlib.java.util; -import java.text.DateFormat; -import java.text.DateFormatSymbols; -import java.text.SimpleDateFormat; import org.teavm.classlib.java.io.TSerializable; import org.teavm.classlib.java.lang.TCloneable; import org.teavm.classlib.java.lang.TComparable; +import org.teavm.classlib.java.text.DateFormat; +import org.teavm.classlib.java.text.DateFormatSymbols; +import org.teavm.classlib.java.text.SimpleDateFormat; public class Date implements TSerializable, TCloneable, TComparable { @@ -104,6 +104,7 @@ public class Date implements TSerializable, TCloneable, TComparable { } } + @Override public int compareTo(Date date) { if (milliseconds < date.milliseconds) { return -1; diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TimeZone.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TimeZone.java index 323727add..9a2ec1fd2 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TimeZone.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TimeZone.java @@ -271,20 +271,6 @@ public abstract class TimeZone implements TSerializable, TCloneable { private static void setICUDefaultTimeZone(TimeZone timezone) { final com.ibm.icu.util.TimeZone icuTZ = com.ibm.icu.util.TimeZone.getTimeZone(timezone.getID()); - - AccessController.doPrivileged(new PrivilegedAction() { - public java.lang.reflect.Field run() { - java.lang.reflect.Field field = null; - try { - field = com.ibm.icu.util.TimeZone.class.getDeclaredField("defaultZone"); - field.setAccessible(true); - field.set("defaultZone", icuTZ); - } catch (Exception e) { - return null; - } - return field; - } - }); } public void setID(String name) {