| package org.unicode.cldr.test; |
| |
| import java.text.ParseException; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.unicode.cldr.test.CheckCLDR.CheckStatus.Subtype; |
| import org.unicode.cldr.test.DisplayAndInputProcessor.NumericType; |
| import org.unicode.cldr.util.CLDRFile; |
| import org.unicode.cldr.util.CldrUtility; |
| import org.unicode.cldr.util.Factory; |
| import org.unicode.cldr.util.ICUServiceBuilder; |
| import org.unicode.cldr.util.PathHeader; |
| import org.unicode.cldr.util.PatternCache; |
| import org.unicode.cldr.util.SupplementalDataInfo; |
| import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo; |
| import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count; |
| import org.unicode.cldr.util.SupplementalDataInfo.PluralType; |
| import org.unicode.cldr.util.XPathParts; |
| |
| import com.google.common.base.Splitter; |
| import com.ibm.icu.text.DecimalFormat; |
| import com.ibm.icu.text.NumberFormat; |
| import com.ibm.icu.text.UnicodeSet; |
| import com.ibm.icu.util.ULocale; |
| |
| public class CheckNumbers extends FactoryCheckCLDR { |
| private static final Splitter SEMI_SPLITTER = Splitter.on(';'); |
| |
| private static final UnicodeSet FORBIDDEN_NUMERIC_PATTERN_CHARS = new UnicodeSet("[[:n:]-[0]]"); |
| |
| /** |
| * If you are going to use ICU services, then ICUServiceBuilder will allow you to create |
| * them entirely from CLDR data, without using the ICU data. |
| */ |
| private ICUServiceBuilder icuServiceBuilder = new ICUServiceBuilder(); |
| |
| private Set<Count> pluralTypes; |
| private Map<Count, Set<Double>> pluralExamples; |
| private Set<String> validNumberingSystems; |
| |
| /** |
| * A number formatter used to show the English format for comparison. |
| */ |
| private static NumberFormat english = NumberFormat.getNumberInstance(ULocale.ENGLISH); |
| static { |
| english.setMaximumFractionDigits(5); |
| } |
| |
| /** |
| * Providing random numbers for some of the tests |
| */ |
| private static Random random = new Random(); |
| |
| private static Pattern ALLOWED_INTEGER = PatternCache.get("1(0+)"); |
| private static Pattern COMMA_ABUSE = PatternCache.get(",[0#]([^0#]|$)"); |
| |
| /** |
| * A MessageFormat string. For display, anything variable that contains strings that might have BIDI |
| * characters in them needs to be surrounded by \u200E. |
| */ |
| static String SampleList = "{0} \u2192 \u201C\u200E{1}\u200E\u201D \u2192 {2}"; |
| |
| /** |
| * Special flag for POSIX locale. |
| */ |
| boolean isPOSIX; |
| |
| public CheckNumbers(Factory factory) { |
| super(factory); |
| } |
| |
| /** |
| * Whenever your test needs initialization, override setCldrFileToCheck. |
| * It is called for each new file needing testing. The first two lines will always |
| * be the same; checking for null, and calling the super. |
| */ |
| @Override |
| public CheckCLDR setCldrFileToCheck(CLDRFile cldrFileToCheck, Options options, |
| List<CheckStatus> possibleErrors) { |
| if (cldrFileToCheck == null) return this; |
| super.setCldrFileToCheck(cldrFileToCheck, options, possibleErrors); |
| icuServiceBuilder.setCldrFile(getResolvedCldrFileToCheck()); |
| isPOSIX = cldrFileToCheck.getLocaleID().indexOf("POSIX") >= 0; |
| SupplementalDataInfo supplementalData = SupplementalDataInfo.getInstance( |
| getFactory().getSupplementalDirectory()); |
| PluralInfo pluralInfo = supplementalData.getPlurals(PluralType.cardinal, cldrFileToCheck.getLocaleID()); |
| pluralTypes = pluralInfo.getCounts(); |
| pluralExamples = pluralInfo.getCountToExamplesMap(); |
| validNumberingSystems = supplementalData.getNumberingSystems(); |
| |
| return this; |
| } |
| |
| /** |
| * This is the method that does the check. Notice that for performance, you should try to |
| * exit as fast as possible except where the path is one that you are testing. |
| */ |
| @Override |
| public CheckCLDR handleCheck(String path, String fullPath, String value, Options options, |
| List<CheckStatus> result) { |
| |
| if (fullPath == null) return this; // skip paths that we don't have |
| // Do a quick check on the currencyMatch, to make sure that it is a proper UnicodeSet |
| if (path.indexOf("/currencyMatch") >= 0) { |
| try { |
| new UnicodeSet(value); |
| } catch (Exception e) { |
| result.add(new CheckStatus().setCause(this).setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.invalidCurrencyMatchSet) |
| .setMessage("Error in creating UnicodeSet {0}; {1}; {2}", |
| new Object[] { value, e.getClass().getName(), e })); |
| } |
| return this; |
| } |
| |
| if (path.indexOf("/minimumGroupingDigits") >= 0) { |
| try { |
| int mgd = Integer.valueOf(value); |
| if (!CldrUtility.DIGITS.contains(value)) { |
| result.add(new CheckStatus().setCause(this).setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.badMinimumGroupingDigits) |
| .setMessage("Minimum grouping digits can only contain Western digits [0-9].")); |
| } else { |
| if (mgd > 4) { |
| result.add(new CheckStatus().setCause(this).setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.badMinimumGroupingDigits) |
| .setMessage("Minimum grouping digits cannot be greater than 4.")); |
| |
| } else if (mgd < 1) { |
| result.add(new CheckStatus().setCause(this).setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.badMinimumGroupingDigits) |
| .setMessage("Minimum grouping digits cannot be less than 1.")); |
| |
| } else if (mgd > 2) { |
| result.add(new CheckStatus().setCause(this).setMainType(CheckStatus.warningType) |
| .setSubtype(Subtype.badMinimumGroupingDigits) |
| .setMessage("Minimum grouping digits > 2 is rare. Please double check this.")); |
| |
| } |
| } |
| } catch (NumberFormatException e) { |
| result.add(new CheckStatus().setCause(this).setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.badMinimumGroupingDigits) |
| .setMessage("Minimum grouping digits must be a numeric value.")); |
| } |
| return this; |
| } |
| |
| if (path.indexOf("defaultNumberingSystem") >= 0 || path.indexOf("otherNumberingSystems") >= 0) { |
| if (!validNumberingSystems.contains(value)) { |
| result.add(new CheckStatus() |
| .setCause(this) |
| .setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.illegalNumberingSystem) |
| .setMessage("Invalid numbering system: " + value)); |
| |
| } |
| } |
| |
| // quick bail from all other cases |
| NumericType type = NumericType.getNumericType(path); |
| if (type == NumericType.NOT_NUMERIC) { |
| return this; // skip |
| } |
| XPathParts parts = XPathParts.getInstance(path); // can't be frozen because some of the following code modifies it! |
| |
| boolean isPositive = true; |
| for (String patternPart : SEMI_SPLITTER.split(value)) { |
| if (!isPositive |
| && !"accounting".equals(parts.getAttributeValue(-2, "type"))) { |
| // must contain the minus sign if not accounting. |
| // String numberSystem = parts.getAttributeValue(2, "numberSystem"); |
| //String minusSign = "-"; // icuServiceBuilder.getMinusSign(numberSystem == null ? "latn" : numberSystem); |
| if (patternPart.indexOf('-') < 0) |
| result.add(new CheckStatus().setCause(this).setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.missingMinusSign) |
| .setMessage("Negative format must contain ASCII minus sign (-).")); |
| |
| } |
| // Make sure currency patterns contain a currency symbol |
| if (type == NumericType.CURRENCY || type == NumericType.CURRENCY_ABBREVIATED) { |
| if (type == NumericType.CURRENCY_ABBREVIATED && value.equals("0")) { |
| // do nothing, not problem |
| } else if (patternPart.indexOf("\u00a4") < 0) { |
| // check for compact format |
| result.add(new CheckStatus().setCause(this).setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.currencyPatternMissingCurrencySymbol) |
| .setMessage("Currency formatting pattern must contain a currency symbol.")); |
| } |
| } |
| |
| // Make sure percent formatting patterns contain a percent symbol, in each part |
| if (type == NumericType.PERCENT) { |
| if (patternPart.indexOf("%") < 0) |
| result.add(new CheckStatus().setCause(this).setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.percentPatternMissingPercentSymbol) |
| .setMessage("Percentage formatting pattern must contain a % symbol.")); |
| } |
| isPositive = false; |
| } |
| |
| // check all |
| if (FORBIDDEN_NUMERIC_PATTERN_CHARS.containsSome(value)) { |
| UnicodeSet chars = new UnicodeSet().addAll(value); |
| chars.retainAll(FORBIDDEN_NUMERIC_PATTERN_CHARS); |
| result.add(new CheckStatus() |
| .setCause(this) |
| .setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.illegalCharactersInNumberPattern) |
| .setMessage("Pattern contains forbidden characters: \u200E{0}\u200E", |
| new Object[] { chars.toPattern(false) })); |
| } |
| |
| // get the final type |
| String lastType = parts.getAttributeValue(-1, "type"); |
| int zeroCount = 0; |
| // it can only be null or an integer of the form 10+ |
| if (lastType != null && !lastType.equals("standard")) { |
| Matcher matcher = ALLOWED_INTEGER.matcher(lastType); |
| if (matcher.matches()) { |
| zeroCount = matcher.end(1) - matcher.start(1); // number of ascii zeros |
| } else { |
| result.add(new CheckStatus().setCause(this).setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.badNumericType) |
| .setMessage("The type of a numeric pattern must be missing or of the form 10....")); |
| } |
| } |
| |
| // Check the validity of the pattern. If this check fails, all other checks |
| // after it will fail, so exit early. |
| UnicodeSet illegalChars = findUnquotedChars(type, value); |
| if (illegalChars != null) { |
| result.add(new CheckStatus().setCause(this) |
| .setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.illegalCharactersInNumberPattern) |
| .setMessage("Pattern contains characters that must be escaped or removed: {0}", new Object[] { illegalChars })); |
| return this; |
| } |
| |
| // Tests that assume that the value is a valid number pattern. |
| // Notice that we pick up any exceptions, so that we can |
| // give a reasonable error message. |
| try { |
| if (type == NumericType.DECIMAL_ABBREVIATED || type == NumericType.CURRENCY_ABBREVIATED) { |
| // Check for consistency in short/long decimal formats. |
| checkDecimalFormatConsistency(parts, path, value, result, type); |
| } else { |
| checkPattern(path, fullPath, value, result, false); |
| } |
| |
| // Check for sane usage of grouping separators. |
| if (COMMA_ABUSE.matcher(value).find()) { |
| result |
| .add(new CheckStatus() |
| .setCause(this) |
| .setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.tooManyGroupingSeparators) |
| .setMessage( |
| "Grouping separator (,) should not be used to group tens. Check if a decimal symbol (.) should have been used instead.")); |
| } else { |
| // check that we have a canonical pattern |
| String pattern = getCanonicalPattern(value, type, zeroCount, isPOSIX); |
| if (!pattern.equals(value)) { |
| result.add(new CheckStatus() |
| .setCause(this).setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.numberPatternNotCanonical) |
| .setMessage("Value should be \u200E{0}\u200E", new Object[] { pattern })); |
| } |
| } |
| |
| } catch (Exception e) { |
| result.add(new CheckStatus().setCause(this).setMainType(CheckStatus.errorType) |
| .setSubtype(Subtype.illegalNumberFormat) |
| .setMessage(e.getMessage() == null ? e.toString() : e.getMessage())); |
| } |
| return this; |
| } |
| |
| /** |
| * Looks for any unquoted non-pattern characters in the specified string |
| * which would make the pattern invalid. |
| * @param type the type of the pattern |
| * @param value the string containing the number pattern |
| * @return the set of unquoted chars in the pattern |
| */ |
| private static UnicodeSet findUnquotedChars(NumericType type, String value) { |
| UnicodeSet chars = new UnicodeSet(); |
| UnicodeSet allowedChars = null; |
| // Allow the digits 1-9 here because they're already checked in another test. |
| if (type == NumericType.DECIMAL_ABBREVIATED) { |
| allowedChars = new UnicodeSet("[0-9]"); |
| } else { |
| allowedChars = new UnicodeSet("[0-9#@.,E+]"); |
| } |
| for (String subPattern : value.split(";")) { |
| // Any unquoted non-special chars are allowed in front of or behind the numerical |
| // symbols, but not in between, e.g. " 0000" is okay but "0 000" is not. |
| int firstIdx = -1; |
| for (int i = 0, len = subPattern.length(); i < len; i++) { |
| char c = subPattern.charAt(i); |
| if (c == '0' || c == '#') { |
| firstIdx = i; |
| break; |
| } |
| } |
| if (firstIdx == -1) { |
| continue; |
| } |
| int lastIdx = Math.max(subPattern.lastIndexOf("0"), subPattern.lastIndexOf('#')); |
| chars.addAll(subPattern.substring(firstIdx, lastIdx)); |
| } |
| chars.removeAll(allowedChars); |
| return chars.size() > 0 ? chars : null; |
| } |
| |
| /** |
| * Override this method if you are going to provide examples of usage. |
| * Only needed for more complicated cases, like number patterns. |
| */ |
| @Override |
| public CheckCLDR handleGetExamples(String path, String fullPath, String value, Options options, List result) { |
| if (path.indexOf("/numbers") < 0) return this; |
| try { |
| if (path.indexOf("/pattern") >= 0 && path.indexOf("/patternDigit") < 0) { |
| checkPattern(path, fullPath, value, result, true); |
| } |
| if (path.indexOf("/currencies") >= 0 && path.endsWith("/symbol")) { |
| checkCurrencyFormats(path, fullPath, value, result, true); |
| } |
| } catch (Exception e) { |
| // don't worry about errors here, they'll be caught above. |
| } |
| return this; |
| } |
| |
| private void checkDecimalFormatConsistency(XPathParts parts, String path, String value, |
| List<CheckStatus> result, NumericType type) { |
| // Look for duplicates of decimal formats with the same number |
| // system and type. |
| // Decimal formats of the same type should have the same number |
| // of integer digits in all the available plural forms. |
| DecimalFormat format = new DecimalFormat(value); |
| int numIntegerDigits = format.getMinimumIntegerDigits(); |
| String countString = parts.getAttributeValue(-1, "count"); |
| Count thisCount = null; |
| try { |
| thisCount = Count.valueOf(countString); |
| } catch (Exception e) { |
| // can happen if count is numeric literal, like "1" |
| } |
| CLDRFile resolvedFile = getResolvedCldrFileToCheck(); |
| Set<String> inconsistentItems = new TreeSet<String>(); |
| Set<Count> otherCounts = new HashSet<Count>(pluralTypes); |
| if (thisCount != null) { |
| if (pluralExamples.get(thisCount).size() == 1 && numIntegerDigits <= 0) { |
| // If a plural case corresponds to a single double value, the format is |
| // allowed to not include a numeric value and in this way be inconsistent |
| // with the numeric formats used for other plural cases. |
| return; |
| } |
| otherCounts.remove(thisCount); |
| } |
| for (Count count : otherCounts) { |
| // System.out.println("## double examples for count " + count + ": " + pluralExamples.get(count)); |
| parts.setAttribute("pattern", "count", count.toString()); |
| String otherPattern = resolvedFile.getWinningValue(parts.toString()); |
| // Ignore the type="other" pattern if not present or invalid. |
| if (otherPattern == null || findUnquotedChars(type, otherPattern) != null) continue; |
| format = new DecimalFormat(otherPattern); |
| int numIntegerDigitsOther = format.getMinimumIntegerDigits(); |
| if (pluralExamples.get(count).size() == 1 && numIntegerDigitsOther <= 0) { |
| // If a plural case corresponds to a single double value, the format is |
| // allowed to not include a numeric value and in this way be inconsistent |
| // with the numeric formats used for other plural cases. |
| continue; |
| } |
| if (numIntegerDigitsOther != numIntegerDigits) { |
| PathHeader pathHeader = getPathHeaderFactory().fromPath(parts.toString()); |
| inconsistentItems.add(pathHeader.getHeaderCode()); |
| } |
| } |
| if (inconsistentItems.size() > 0) { |
| // Get label for items of this type by removing the count. |
| PathHeader pathHeader = getPathHeaderFactory().fromPath(path.substring(0, path.lastIndexOf('['))); |
| String groupHeaderString = pathHeader.getHeaderCode(); |
| boolean isWinningValue = resolvedFile.getWinningValue(path).equals(value); |
| result.add(new CheckStatus().setCause(this) |
| .setMainType(isWinningValue ? CheckStatus.errorType : CheckStatus.warningType) |
| .setSubtype(Subtype.inconsistentPluralFormat) |
| .setMessage("All values for {0} must have the same number of digits. " + |
| "The number of zeros in this pattern is inconsistent with the following: {1}.", |
| groupHeaderString, |
| inconsistentItems.toString())); |
| } |
| } |
| |
| /** |
| * This method builds a decimal format (based on whether the pattern is for currencies or not) |
| * and tests samples. |
| */ |
| private void checkPattern(String path, String fullPath, String value, List result, boolean generateExamples) |
| throws ParseException { |
| if (value.indexOf('\u00a4') >= 0) { // currency pattern |
| DecimalFormat x = icuServiceBuilder.getCurrencyFormat("XXX"); |
| addOrTestSamples(x, x.toPattern(), value, result, generateExamples); |
| } else { |
| DecimalFormat x = icuServiceBuilder.getNumberFormat(value); |
| addOrTestSamples(x, value, "", result, generateExamples); |
| } |
| } |
| |
| /** |
| * Check some currency patterns. |
| */ |
| private void checkCurrencyFormats(String path, String fullPath, String value, List result, boolean generateExamples) |
| throws ParseException { |
| DecimalFormat x = icuServiceBuilder.getCurrencyFormat(CLDRFile.getCode(path)); |
| addOrTestSamples(x, x.toPattern(), value, result, generateExamples); |
| } |
| |
| /** |
| * Generates some samples. If we are producing examples, these are used for that; otherwise |
| * they are just tested. |
| */ |
| private void addOrTestSamples(DecimalFormat x, String pattern, String context, List result, boolean generateExamples) |
| throws ParseException { |
| // Object[] arguments = new Object[3]; |
| // |
| // double sample = getRandomNumber(); |
| // arguments[0] = String.valueOf(sample); |
| // String formatted = x.format(sample); |
| // arguments[1] = formatted; |
| // boolean gotFailure = false; |
| // try { |
| // parsePosition.setIndex(0); |
| // double parsed = x.parse(formatted, parsePosition).doubleValue(); |
| // if (parsePosition.getIndex() != formatted.length()) { |
| // arguments[2] = "Couldn't parse past: " + "\u200E" + formatted.substring(0,parsePosition.getIndex()) + |
| // "\u200E"; |
| // gotFailure = true; |
| // } else { |
| // arguments[2] = String.valueOf(parsed); |
| // } |
| // } catch (Exception e) { |
| // arguments[2] = e.getMessage(); |
| // gotFailure = true; |
| // } |
| // htmlMessage.append(pattern1) |
| // .append(TransliteratorUtilities.toXML.transliterate(String.valueOf(sample))) |
| // .append(pattern2) |
| // .append(TransliteratorUtilities.toXML.transliterate(formatted)) |
| // .append(pattern3) |
| // .append(TransliteratorUtilities.toXML.transliterate(String.valueOf(parsed))) |
| // .append(pattern4); |
| // if (generateExamples || gotFailure) { |
| // result.add(new CheckStatus() |
| // .setCause(this).setType(CheckStatus.exampleType) |
| // .setMessage(SampleList, arguments)); |
| // } |
| if (generateExamples) { |
| result.add(new MyCheckStatus() |
| .setFormat(x, context) |
| .setCause(this).setMainType(CheckStatus.demoType)); |
| } |
| } |
| |
| /** |
| * Generate a randome number for testing, with a certain number of decimal places, and |
| * half the time negative |
| */ |
| private static double getRandomNumber() { |
| // min = 12345.678 |
| double rand = random.nextDouble(); |
| // System.out.println(rand); |
| double sample = Math.round(rand * 100000.0 * 1000.0) / 1000.0 + 10000.0; |
| if (random.nextBoolean()) sample = -sample; |
| return sample; |
| } |
| |
| /* |
| * static String pattern1 = |
| * "<table border='1' cellpadding='2' cellspacing='0' style='border-collapse: collapse' style='width: 100%'>" |
| * + "<tr>" |
| * + "<td nowrap width='1%'>Input:</td>" |
| * + "<td><input type='text' name='T1' size='50' style='width: 100%' value='"; |
| * static String pattern2 = "'></td>" |
| * + "<td nowrap width='1%'><input type='submit' value='Test' name='B1'></td>" |
| * + "<td nowrap width='1%'>Formatted:</td>" |
| * + "<td><input type='text' name='T2' size='50' style='width: 100%' value='"; |
| * static String pattern3 = "'></td>" |
| * + "<td nowrap width='1%'>Parsed:</td>" |
| * + "<td><input type='text' name='T3' size='50' style='width: 100%' value='"; |
| * static String pattern4 = "'></td>" |
| * + "</tr>" |
| * + "</table>"; |
| */ |
| |
| /** |
| * Produce a canonical pattern, which will vary according to type and whether it is posix or not. |
| * |
| * @param path |
| */ |
| public static String getCanonicalPattern(String inpattern, NumericType type, int zeroCount, boolean isPOSIX) { |
| // TODO fix later to properly handle quoted ; |
| DecimalFormat df = new DecimalFormat(inpattern); |
| String pattern; |
| |
| if (zeroCount == 0) { |
| int[] digits = isPOSIX ? type.getPosixDigitCount() : type.getDigitCount(); |
| df.setMinimumIntegerDigits(digits[0]); |
| df.setMinimumFractionDigits(digits[1]); |
| df.setMaximumFractionDigits(digits[2]); |
| pattern = df.toPattern(); |
| } else { // of form 1000. Result must be 0+(.0+)? |
| if (type == NumericType.CURRENCY_ABBREVIATED) { |
| if (!inpattern.contains("0.0")) { |
| df.setMinimumFractionDigits(0); // correct the current rewrite |
| } |
| } |
| df.setMaximumFractionDigits(df.getMinimumFractionDigits()); |
| int minimumIntegerDigits = df.getMinimumIntegerDigits(); |
| if (minimumIntegerDigits < 1) minimumIntegerDigits = 1; |
| df.setMaximumIntegerDigits(minimumIntegerDigits); |
| pattern = df.toPattern(); |
| } |
| |
| // int pos = pattern.indexOf(';'); |
| // if (pos < 0) return pattern + ";-" + pattern; |
| return pattern; |
| } |
| |
| /** |
| * You don't normally need this, unless you are doing a demo also. |
| */ |
| static public class MyCheckStatus extends CheckStatus { |
| private DecimalFormat df; |
| String context; |
| |
| public MyCheckStatus setFormat(DecimalFormat df, String context) { |
| this.df = df; |
| this.context = context; |
| return this; |
| } |
| |
| public SimpleDemo getDemo() { |
| return new MyDemo().setFormat(df); |
| } |
| } |
| |
| /** |
| * Here is how to do a demo. |
| * You provide the function getArguments that takes in-and-out parameters. |
| */ |
| static class MyDemo extends FormatDemo { |
| private DecimalFormat df; |
| |
| protected String getPattern() { |
| return df.toPattern(); |
| } |
| |
| protected String getSampleInput() { |
| return String.valueOf(ExampleGenerator.NUMBER_SAMPLE); |
| } |
| |
| public MyDemo setFormat(DecimalFormat df) { |
| this.df = df; |
| return this; |
| } |
| |
| protected void getArguments(Map<String, String> inout) { |
| currentPattern = currentInput = currentFormatted = currentReparsed = "?"; |
| double d; |
| try { |
| currentPattern = inout.get("pattern"); |
| if (currentPattern != null) |
| df.applyPattern(currentPattern); |
| else |
| currentPattern = getPattern(); |
| } catch (Exception e) { |
| currentPattern = "Use format like: ##,###.##"; |
| return; |
| } |
| try { |
| currentInput = inout.get("input"); |
| if (currentInput == null) { |
| currentInput = getSampleInput(); |
| } |
| d = Double.parseDouble(currentInput); |
| } catch (Exception e) { |
| currentInput = "Use English format: 1234.56"; |
| return; |
| } |
| try { |
| currentFormatted = df.format(d); |
| } catch (Exception e) { |
| currentFormatted = "Can't format: " + e.getMessage(); |
| return; |
| } |
| try { |
| parsePosition.setIndex(0); |
| Number n = df.parse(currentFormatted, parsePosition); |
| if (parsePosition.getIndex() != currentFormatted.length()) { |
| currentReparsed = "Couldn't parse past: \u200E" |
| + currentFormatted.substring(0, parsePosition.getIndex()) + "\u200E"; |
| } else { |
| currentReparsed = n.toString(); |
| } |
| } catch (Exception e) { |
| currentReparsed = "Can't parse: " + e.getMessage(); |
| } |
| } |
| |
| } |
| } |