From 408347e4608c12de6f63b4df13dcebbf19c5306b Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 1 Jun 2015 00:21:41 +0300 Subject: [PATCH] Implement decimal pattern parser --- .../classlib/java/text/TDecimalFormat.java | 124 ++++++++- .../java/text/TDecimalFormatParser.java | 241 ++++++++++++++++++ .../teavm/classlib/java/util/TObservable.java | 13 +- .../classlib/java/text/DecimalFormatTest.java | 112 ++++++++ 4 files changed, 480 insertions(+), 10 deletions(-) create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatParser.java create mode 100644 teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java index 50d16ad12..d39248c14 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java @@ -25,7 +25,16 @@ import org.teavm.classlib.java.util.TLocale; * @author Alexey Andreev */ public class TDecimalFormat extends TNumberFormat { - private TDecimalFormatSymbols symbols; + TDecimalFormatSymbols symbols; + private String positivePrefix; + private String negativePrefix; + private String positiveSuffix; + private String negativeSuffix; + private int multiplier; + private int groupingSize; + private boolean decimalSeparatorAlwaysShown; + private boolean parseBigDecimal; + int exponentDigits; public TDecimalFormat() { this(CLDRHelper.resolveDecimalFormat(TLocale.getDefault().getLanguage(), TLocale.getDefault().getCountry())); @@ -47,8 +56,10 @@ public class TDecimalFormat extends TNumberFormat { super.setMinimumIntegerDigits(decimalData.getMinimumIntegerDigits()); } - public void applyPattern(@SuppressWarnings("unused") String pattern) { - + public void applyPattern(String pattern) { + TDecimalFormatParser parser = new TDecimalFormatParser(); + parser.parse(pattern); + parser.apply(this); } public DecimalFormatSymbols getDecimalFormatSymbols() { @@ -69,4 +80,111 @@ public class TDecimalFormat extends TNumberFormat { public StringBuffer format(double value, StringBuffer buffer, TFieldPosition field) { return null; } + + public String getPositivePrefix() { + return positivePrefix; + } + + public void setPositivePrefix(String newValue) { + positivePrefix = newValue; + } + + public String getNegativePrefix() { + return negativePrefix; + } + + public void setNegativePrefix(String newValue) { + negativePrefix = newValue; + } + + public String getPositiveSuffix() { + return positiveSuffix; + } + + public void setPositiveSuffix(String newValue) { + positiveSuffix = newValue; + } + + public String getNegativeSuffix() { + return negativeSuffix; + } + + public void setNegativeSuffix(String newValue) { + negativeSuffix = newValue; + } + + public int getMultiplier() { + return multiplier; + } + + public void setMultiplier(int newValue) { + multiplier = newValue; + } + + public int getGroupingSize() { + return groupingSize; + } + + public void setGroupingSize(int newValue) { + groupingSize = newValue; + } + + public boolean isDecimalSeparatorAlwaysShown() { + return decimalSeparatorAlwaysShown; + } + + public void setDecimalSeparatorAlwaysShown(boolean newValue) { + decimalSeparatorAlwaysShown = newValue; + } + + public boolean isParseBigDecimal() { + return parseBigDecimal; + } + + public void setParseBigDecimal(boolean newValue) { + parseBigDecimal = newValue; + } + + @Override + public Object clone() { + return super.clone(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof TDecimalFormat)) { + return false; + } + TDecimalFormat other = (TDecimalFormat)obj; + if (!super.equals(obj)) { + return false; + } + return positivePrefix.equals(other.positivePrefix) && + positiveSuffix.equals(other.positiveSuffix) && + negativePrefix.equals(other.negativePrefix) && + negativeSuffix.equals(other.negativeSuffix) && + multiplier == other.multiplier && + groupingSize == other.groupingSize && + decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown && + parseBigDecimal == other.parseBigDecimal && + exponentDigits == other.exponentDigits; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = result * 31 + positivePrefix.hashCode(); + result = result * 31 + positiveSuffix.hashCode(); + result = result * 31 + negativePrefix.hashCode(); + result = result * 31 + negativeSuffix.hashCode(); + result = result * 31 + multiplier; + result = result * 31 + groupingSize; + result = result * 31 + (decimalSeparatorAlwaysShown ? 1 : 0); + result = result * 31 + (parseBigDecimal ? 1 : 0); + result = result * 31 + exponentDigits; + return result; + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatParser.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatParser.java new file mode 100644 index 000000000..7a17ef032 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatParser.java @@ -0,0 +1,241 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.text; + +/** + * + * @author Alexey Andreev + */ +class TDecimalFormatParser { + private String positivePrefix; + private String positiveSuffix; + private String negativePrefix; + private String negativeSuffix; + private int groupSize; + private int minimumIntLength; + private int intLength; + private int minimumFracLength; + private int fracLength; + private int exponentLength; + private boolean decimalSeparatorRequired; + private String string; + private int index; + + public void parse(String string) { + groupSize = 0; + minimumFracLength = 0; + fracLength = 0; + exponentLength = 0; + decimalSeparatorRequired = false; + this.string = string; + index = 0; + positivePrefix = parseText(false, false); + if (index == string.length()) { + throw new IllegalArgumentException("Positive number pattern not found in " + string); + } + parseNumber(true); + if (index < string.length() && string.charAt(index) != ';') { + positiveSuffix = parseText(true, false); + } + if (index < string.length()) { + if (string.charAt(index++) != ';') { + throw new IllegalArgumentException("Expected ';' at " + index + " in " + string); + } + negativePrefix = parseText(false, true); + parseNumber(false); + negativeSuffix = parseText(true, true); + } + } + + public void apply(TDecimalFormat format) { + format.setPositivePrefix(positivePrefix); + format.setPositiveSuffix(positiveSuffix); + format.setNegativePrefix(negativePrefix != null ? negativePrefix : + format.symbols.getMinusSign() + positivePrefix); + format.setNegativeSuffix(negativeSuffix != null ? negativeSuffix : positiveSuffix); + format.setGroupingSize(groupSize); + format.setGroupingUsed(groupSize > 0); + format.setMinimumIntegerDigits(minimumIntLength); + format.setMaximumIntegerDigits(intLength); + format.setMinimumFractionDigits(minimumFracLength); + format.setMaximumFractionDigits(fracLength); + format.setDecimalSeparatorAlwaysShown(decimalSeparatorRequired); + format.exponentDigits = exponentLength; + } + + private String parseText(boolean suffix, boolean end) { + StringBuilder sb = new StringBuilder(); + loop: while (index < string.length()) { + char c = string.charAt(index); + switch (c) { + case '#': + case '0': + if (suffix) { + throw new IllegalArgumentException("Prefix contains special character at " + index + " in " + + string); + } + break loop; + case ';': + if (end) { + throw new IllegalArgumentException("Prefix contains special character at " + index + " in " + + string); + } + break loop; + case '.': + case 'E': + throw new IllegalArgumentException("Prefix contains special character at " + index + " in " + + string); + case '\'': { + ++index; + int next = string.indexOf('\'', index); + if (next < 0) { + throw new IllegalArgumentException("Quote opened at " + index + " was not closed in " + + string); + } + if (next == index) { + sb.append('\''); + } else { + sb.append(string.substring(index, next)); + } + index = next + 1; + break; + } + default: + sb.append(c); + ++index; + break; + } + } + return sb.toString(); + } + + private void parseNumber(boolean apply) { + parseIntegerPart(apply); + if (index < string.length() && string.charAt(index) == '.') { + ++index; + parseFractionalPart(apply); + } + if (index < string.length() && string.charAt(index) == 'E') { + ++index; + parseExponent(apply); + } + } + + private void parseIntegerPart(boolean apply) { + int lastGroup = index; + boolean optionalDigits = true; + int length = 0; + int minimumLength = 0; + loop: while (index < string.length()) { + switch (string.charAt(index)) { + case '#': + if (!optionalDigits) { + throw new IllegalArgumentException("Unexpected '#' at non-optional digit part at " + index + + " in " + string); + } + ++length; + break; + case ',': + if (lastGroup + 1 == index) { + throw new IllegalArgumentException("Two commas at " + index + " in " + string); + } + if (apply) { + groupSize = index - lastGroup; + } + break; + case '0': + optionalDigits = false; + ++length; + ++minimumLength; + break; + default: + break loop; + } + ++index; + } + if (length == 0) { + throw new IllegalArgumentException("Pattern does not specify integer digits at " + index + + " in " + string); + } + if (apply) { + intLength = length; + minimumIntLength = minimumLength; + } + } + + private void parseFractionalPart(boolean apply) { + boolean optionalDigits = false; + int length = 0; + int minimumLength = 0; + loop: while (index < string.length()) { + switch (string.charAt(index)) { + case '#': + ++length; + optionalDigits = true; + break; + case ',': + throw new IllegalArgumentException("Group separator found at fractional part at " + index + + " in " + string); + case '0': + if (!optionalDigits) { + throw new IllegalArgumentException("Unexpected '0' at optional digit part at " + index + + " in " + string); + } + ++length; + ++minimumLength; + break; + case '.': + throw new IllegalArgumentException("Unexpected second decimal separator at " + index + + " in " + string); + default: + break loop; + } + ++index; + } + if (apply) { + fracLength = length; + minimumFracLength = minimumLength; + decimalSeparatorRequired = true; + } + } + + private void parseExponent(boolean apply) { + int length = 0; + loop: while (index < string.length()) { + switch (string.charAt(index)) { + case '#': + case ',': + case '.': + case 'E': + throw new IllegalArgumentException("Unexpected char at exponent at " + index + + " in " + string); + case '0': + ++length; + break; + default: + break loop; + } + ++index; + } + if (length == 0) { + throw new IllegalArgumentException("Pattern does not specify exponent digits at " + index + + " in " + string); + } + if (apply) { + exponentLength = length; + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TObservable.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TObservable.java index dc65d1316..f8af26322 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TObservable.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TObservable.java @@ -20,7 +20,7 @@ package org.teavm.classlib.java.util; * @author shannah */ public class TObservable { - TList observers = new TArrayList(); + TList observers = new TArrayList<>(); boolean changed = false; @@ -34,7 +34,7 @@ public class TObservable { /** * Adds the specified observer to the list of observers. If it is already * registered, it is not added a second time. - * + * * @param observer * the Observer to add. */ @@ -58,7 +58,7 @@ public class TObservable { /** * Returns the number of observers registered to this {@code Observable}. - * + * * @return the number of observers. */ public int countObservers() { @@ -68,7 +68,7 @@ public class TObservable { /** * Removes the specified observer from the list of observers. Passing null * won't do anything. - * + * * @param observer * the observer to remove. */ @@ -85,7 +85,7 @@ public class TObservable { /** * Returns the changed flag for this {@code Observable}. - * + * * @return {@code true} when the changed flag for this {@code Observable} is * set, {@code false} otherwise. */ @@ -108,11 +108,10 @@ public class TObservable { * If {@code hasChanged()} returns {@code true}, calls the {@code update()} * method for every Observer in the list of observers using the specified * argument. Afterwards calls {@code clearChanged()}. - * + * * @param data * the argument passed to {@code update()}. */ - @SuppressWarnings("unchecked") public void notifyObservers(Object data) { int size = 0; TObserver[] arrays = null; diff --git a/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java b/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java new file mode 100644 index 000000000..6ec6ee754 --- /dev/null +++ b/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java @@ -0,0 +1,112 @@ +package org.teavm.classlib.java.text; + +import static org.junit.Assert.*; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class DecimalFormatTest { + @Test + public void parsesIntegerPattern() { + DecimalFormat format = new DecimalFormat("00"); + assertEquals(2, format.getMinimumIntegerDigits()); + assertFalse(format.isDecimalSeparatorAlwaysShown()); + assertFalse(format.isGroupingUsed()); + assertEquals(0, format.getGroupingSize()); + assertEquals(0, format.getMinimumFractionDigits()); + assertEquals(0, format.getMaximumFractionDigits()); + + format = new DecimalFormat("##"); + assertEquals(0, format.getMinimumIntegerDigits()); + assertFalse(format.isDecimalSeparatorAlwaysShown()); + assertFalse(format.isGroupingUsed()); + assertEquals(0, format.getGroupingSize()); + assertEquals(0, format.getMinimumFractionDigits()); + assertEquals(0, format.getMaximumFractionDigits()); + + format = new DecimalFormat("#,##0"); + assertEquals(1, format.getMinimumIntegerDigits()); + assertFalse(format.isDecimalSeparatorAlwaysShown()); + assertTrue(format.isGroupingUsed()); + assertEquals(3, format.getGroupingSize()); + assertEquals(0, format.getMinimumFractionDigits()); + assertEquals(0, format.getMaximumFractionDigits()); + } + + @Test + public void selectsLastGrouping() { + DecimalFormat format = new DecimalFormat("#,0,000"); + assertEquals(4, format.getMinimumIntegerDigits()); + assertTrue(format.isGroupingUsed()); + assertEquals(3, format.getGroupingSize()); + } + + @Test + public void parsesPrefixAndSuffixInPattern() { + DecimalFormat format = new DecimalFormat("(00)", new DecimalFormatSymbols(Locale.ENGLISH)); + assertEquals(2, format.getMinimumIntegerDigits()); + assertEquals("(", format.getPositivePrefix()); + assertEquals(")", format.getPositiveSuffix()); + assertEquals("-(", format.getNegativePrefix()); + assertEquals(")", format.getNegativeSuffix()); + + format = new DecimalFormat("+(00);-{#}", new DecimalFormatSymbols(Locale.ENGLISH)); + assertEquals(2, format.getMinimumIntegerDigits()); + assertEquals("+(", format.getPositivePrefix()); + assertEquals(")", format.getPositiveSuffix()); + assertEquals("-{", format.getNegativePrefix()); + } + + @Test + public void parsesFractionalPattern() { + DecimalFormat format = new DecimalFormat("#."); + assertEquals(1, format.getMinimumIntegerDigits()); + assertTrue(format.isDecimalSeparatorAlwaysShown()); + assertFalse(format.isGroupingUsed()); + assertEquals(0, format.getGroupingSize()); + assertEquals(0, format.getMinimumFractionDigits()); + assertEquals(0, format.getMaximumFractionDigits()); + + format = new DecimalFormat("#.00"); + assertEquals(0, format.getMinimumIntegerDigits()); + assertFalse(format.isGroupingUsed()); + assertEquals(0, format.getGroupingSize()); + assertEquals(2, format.getMinimumFractionDigits()); + assertEquals(2, format.getMaximumFractionDigits()); + + format = new DecimalFormat("#.00##"); + assertEquals(0, format.getMinimumIntegerDigits()); + assertFalse(format.isGroupingUsed()); + assertEquals(0, format.getGroupingSize()); + assertEquals(2, format.getMinimumFractionDigits()); + assertEquals(4, format.getMaximumFractionDigits()); + + format = new DecimalFormat("#00.00##"); + assertEquals(2, format.getMinimumIntegerDigits()); + assertFalse(format.isGroupingUsed()); + assertEquals(0, format.getGroupingSize()); + assertEquals(2, format.getMinimumFractionDigits()); + assertEquals(4, format.getMaximumFractionDigits()); + + format = new DecimalFormat("#,#00.00##"); + assertEquals(2, format.getMinimumIntegerDigits()); + assertTrue(format.isGroupingUsed()); + assertEquals(3, format.getGroupingSize()); + assertEquals(2, format.getMinimumFractionDigits()); + assertEquals(4, format.getMaximumFractionDigits()); + } + + @Test + public void parsesExponentialPattern() { + DecimalFormat format = new DecimalFormat("##0E00"); + assertEquals(1, format.getMinimumIntegerDigits()); + assertEquals(0, format.getGroupingSize()); + assertEquals(0, format.getMinimumFractionDigits()); + assertEquals(0, format.getMaximumFractionDigits()); + } +}