Implement decimal pattern parser

This commit is contained in:
Alexey Andreev 2015-06-01 00:21:41 +03:00
parent 1fd4b8ff7a
commit 408347e460
4 changed files with 480 additions and 10 deletions

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -20,7 +20,7 @@ package org.teavm.classlib.java.util;
* @author shannah
*/
public class TObservable {
TList<TObserver> observers = new TArrayList<TObserver>();
TList<TObserver> 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;

View File

@ -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());
}
}