diff --git a/src/share/classes/java/text/MessageFormat.java b/src/share/classes/java/text/MessageFormat.java index 41623c48ed06ee9f0d878b25605df2542076a444..6590e7677952f3dd347e00186d0b4db65789e640 100644 --- a/src/share/classes/java/text/MessageFormat.java +++ b/src/share/classes/java/text/MessageFormat.java @@ -423,18 +423,19 @@ public class MessageFormat extends Format { * @exception IllegalArgumentException if the pattern is invalid */ public void applyPattern(String pattern) { - StringBuffer[] segments = new StringBuffer[4]; - for (int i = 0; i < segments.length; ++i) { - segments[i] = new StringBuffer(); - } - int part = 0; + StringBuilder[] segments = new StringBuilder[4]; + // Allocate only segments[SEG_RAW] here. The rest are + // allocated on demand. + segments[SEG_RAW] = new StringBuilder(); + + int part = SEG_RAW; int formatNumber = 0; boolean inQuote = false; int braceStack = 0; maxOffset = -1; for (int i = 0; i < pattern.length(); ++i) { char ch = pattern.charAt(i); - if (part == 0) { + if (part == SEG_RAW) { if (ch == '\'') { if (i + 1 < pattern.length() && pattern.charAt(i+1) == '\'') { @@ -444,43 +445,61 @@ public class MessageFormat extends Format { inQuote = !inQuote; } } else if (ch == '{' && !inQuote) { - part = 1; + part = SEG_INDEX; + if (segments[SEG_INDEX] == null) { + segments[SEG_INDEX] = new StringBuilder(); + } } else { segments[part].append(ch); } - } else if (inQuote) { // just copy quotes in parts - segments[part].append(ch); - if (ch == '\'') { - inQuote = false; - } - } else { - switch (ch) { - case ',': - if (part < 3) - part += 1; - else - segments[part].append(ch); - break; - case '{': - ++braceStack; + } else { + if (inQuote) { // just copy quotes in parts segments[part].append(ch); - break; - case '}': - if (braceStack == 0) { - part = 0; - makeFormat(i, formatNumber, segments); - formatNumber++; - } else { - --braceStack; + if (ch == '\'') { + inQuote = false; + } + } else { + switch (ch) { + case ',': + if (part < SEG_MODIFIER) { + if (segments[++part] == null) { + segments[part] = new StringBuilder(); + } + } else { + segments[part].append(ch); + } + break; + case '{': + ++braceStack; segments[part].append(ch); + break; + case '}': + if (braceStack == 0) { + part = SEG_RAW; + makeFormat(i, formatNumber, segments); + formatNumber++; + // throw away other segments + segments[SEG_INDEX] = null; + segments[SEG_TYPE] = null; + segments[SEG_MODIFIER] = null; + } else { + --braceStack; + segments[part].append(ch); + } + break; + case ' ': + // Skip any leading space chars for SEG_TYPE. + if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) { + segments[part].append(ch); + } + break; + case '\'': + inQuote = true; + // fall through, so we keep quotes in other parts + default: + segments[part].append(ch); + break; } - break; - case '\'': - inQuote = true; - // fall through, so we keep quotes in other parts - default: - segments[part].append(ch); - break; } } } @@ -502,65 +521,57 @@ public class MessageFormat extends Format { public String toPattern() { // later, make this more extensible int lastOffset = 0; - StringBuffer result = new StringBuffer(); + StringBuilder result = new StringBuilder(); for (int i = 0; i <= maxOffset; ++i) { - copyAndFixQuotes(pattern, lastOffset, offsets[i],result); + copyAndFixQuotes(pattern, lastOffset, offsets[i], result); lastOffset = offsets[i]; - result.append('{'); - result.append(argumentNumbers[i]); - if (formats[i] == null) { + result.append('{').append(argumentNumbers[i]); + Format fmt = formats[i]; + if (fmt == null) { // do nothing, string format - } else if (formats[i] instanceof DecimalFormat) { - if (formats[i].equals(NumberFormat.getInstance(locale))) { + } else if (fmt instanceof NumberFormat) { + if (fmt.equals(NumberFormat.getInstance(locale))) { result.append(",number"); - } else if (formats[i].equals(NumberFormat.getCurrencyInstance(locale))) { + } else if (fmt.equals(NumberFormat.getCurrencyInstance(locale))) { result.append(",number,currency"); - } else if (formats[i].equals(NumberFormat.getPercentInstance(locale))) { + } else if (fmt.equals(NumberFormat.getPercentInstance(locale))) { result.append(",number,percent"); - } else if (formats[i].equals(NumberFormat.getIntegerInstance(locale))) { + } else if (fmt.equals(NumberFormat.getIntegerInstance(locale))) { result.append(",number,integer"); } else { - result.append(",number," + - ((DecimalFormat)formats[i]).toPattern()); + if (fmt instanceof DecimalFormat) { + result.append(",number,").append(((DecimalFormat)fmt).toPattern()); + } else if (fmt instanceof ChoiceFormat) { + result.append(",choice,").append(((ChoiceFormat)fmt).toPattern()); + } else { + // UNKNOWN + } } - } else if (formats[i] instanceof SimpleDateFormat) { - if (formats[i].equals(DateFormat.getDateInstance( - DateFormat.DEFAULT,locale))) { - result.append(",date"); - } else if (formats[i].equals(DateFormat.getDateInstance( - DateFormat.SHORT,locale))) { - result.append(",date,short"); - } else if (formats[i].equals(DateFormat.getDateInstance( - DateFormat.DEFAULT,locale))) { - result.append(",date,medium"); - } else if (formats[i].equals(DateFormat.getDateInstance( - DateFormat.LONG,locale))) { - result.append(",date,long"); - } else if (formats[i].equals(DateFormat.getDateInstance( - DateFormat.FULL,locale))) { - result.append(",date,full"); - } else if (formats[i].equals(DateFormat.getTimeInstance( - DateFormat.DEFAULT,locale))) { - result.append(",time"); - } else if (formats[i].equals(DateFormat.getTimeInstance( - DateFormat.SHORT,locale))) { - result.append(",time,short"); - } else if (formats[i].equals(DateFormat.getTimeInstance( - DateFormat.DEFAULT,locale))) { - result.append(",time,medium"); - } else if (formats[i].equals(DateFormat.getTimeInstance( - DateFormat.LONG,locale))) { - result.append(",time,long"); - } else if (formats[i].equals(DateFormat.getTimeInstance( - DateFormat.FULL,locale))) { - result.append(",time,full"); - } else { - result.append(",date," - + ((SimpleDateFormat)formats[i]).toPattern()); + } else if (fmt instanceof DateFormat) { + int index; + for (index = MODIFIER_DEFAULT; index < DATE_TIME_MODIFIERS.length; index++) { + DateFormat df = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[index], + locale); + if (fmt.equals(df)) { + result.append(",date"); + break; + } + df = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[index], + locale); + if (fmt.equals(df)) { + result.append(",time"); + break; + } + } + if (index >= DATE_TIME_MODIFIERS.length) { + if (fmt instanceof SimpleDateFormat) { + result.append(",date,").append(((SimpleDateFormat)fmt).toPattern()); + } else { + // UNKNOWN + } + } else if (index != MODIFIER_DEFAULT) { + result.append(',').append(DATE_TIME_MODIFIER_KEYWORDS[index]); } - } else if (formats[i] instanceof ChoiceFormat) { - result.append(",choice," - + ((ChoiceFormat)formats[i]).toPattern()); } else { //result.append(", unknown"); } @@ -674,7 +685,7 @@ public class MessageFormat extends Format { * * @param formatElementIndex the index of a format element within the pattern * @param newFormat the format to use for the specified format element - * @exception ArrayIndexOutOfBoundsException if formatElementIndex is equal to or + * @exception ArrayIndexOutOfBoundsException if {@code formatElementIndex} is equal to or * larger than the number of format elements in the pattern string */ public void setFormat(int formatElementIndex, Format newFormat) { @@ -968,7 +979,8 @@ public class MessageFormat extends Format { if (patternOffset >= tempLength) { next = source.length(); }else{ - next = source.indexOf( pattern.substring(patternOffset,tempLength), sourceOffset); + next = source.indexOf(pattern.substring(patternOffset, tempLength), + sourceOffset); } if (next < 0) { @@ -1222,7 +1234,7 @@ public class MessageFormat extends Format { lastOffset = offsets[i]; int argumentNumber = argumentNumbers[i]; if (arguments == null || argumentNumber >= arguments.length) { - result.append("{" + argumentNumber + "}"); + result.append('{').append(argumentNumber).append('}'); continue; } // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3); @@ -1334,25 +1346,83 @@ public class MessageFormat extends Format { } } - private static final String[] typeList = - {"", "", "number", "", "date", "", "time", "", "choice"}; - private static final String[] modifierList = - {"", "", "currency", "", "percent", "", "integer"}; - private static final String[] dateModifierList = - {"", "", "short", "", "medium", "", "long", "", "full"}; + // Indices for segments + private static final int SEG_RAW = 0; + private static final int SEG_INDEX = 1; + private static final int SEG_TYPE = 2; + private static final int SEG_MODIFIER = 3; // modifier or subformat + + // Indices for type keywords + private static final int TYPE_NULL = 0; + private static final int TYPE_NUMBER = 1; + private static final int TYPE_DATE = 2; + private static final int TYPE_TIME = 3; + private static final int TYPE_CHOICE = 4; + + private static final String[] TYPE_KEYWORDS = { + "", + "number", + "date", + "time", + "choice" + }; + + // Indices for number modifiers + private static final int MODIFIER_DEFAULT = 0; // common in number and date-time + private static final int MODIFIER_CURRENCY = 1; + private static final int MODIFIER_PERCENT = 2; + private static final int MODIFIER_INTEGER = 3; + + private static final String[] NUMBER_MODIFIER_KEYWORDS = { + "", + "currency", + "percent", + "integer" + }; + + // Indices for date-time modifiers + private static final int MODIFIER_SHORT = 1; + private static final int MODIFIER_MEDIUM = 2; + private static final int MODIFIER_LONG = 3; + private static final int MODIFIER_FULL = 4; + + private static final String[] DATE_TIME_MODIFIER_KEYWORDS = { + "", + "short", + "medium", + "long", + "full" + }; + + // Date-time style values corresponding to the date-time modifiers. + private static final int[] DATE_TIME_MODIFIERS = { + DateFormat.DEFAULT, + DateFormat.SHORT, + DateFormat.MEDIUM, + DateFormat.LONG, + DateFormat.FULL, + }; private void makeFormat(int position, int offsetNumber, - StringBuffer[] segments) + StringBuilder[] textSegments) { + String[] segments = new String[textSegments.length]; + for (int i = 0; i < textSegments.length; i++) { + StringBuilder oneseg = textSegments[i]; + segments[i] = (oneseg != null) ? oneseg.toString() : ""; + } + // get the argument number int argumentNumber; try { - argumentNumber = Integer.parseInt(segments[1].toString()); // always unlocalized! + argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always unlocalized! } catch (NumberFormatException e) { - throw new IllegalArgumentException("can't parse argument number: " + segments[1]); + throw new IllegalArgumentException("can't parse argument number: " + + segments[SEG_INDEX], e); } if (argumentNumber < 0) { - throw new IllegalArgumentException("negative argument number: " + argumentNumber); + throw new IllegalArgumentException("negative argument number: " + + argumentNumber); } // resize format information arrays if necessary @@ -1370,120 +1440,129 @@ public class MessageFormat extends Format { } int oldMaxOffset = maxOffset; maxOffset = offsetNumber; - offsets[offsetNumber] = segments[0].length(); + offsets[offsetNumber] = segments[SEG_RAW].length(); argumentNumbers[offsetNumber] = argumentNumber; // now get the format Format newFormat = null; - switch (findKeyword(segments[2].toString(), typeList)) { - case 0: - break; - case 1: case 2:// number - switch (findKeyword(segments[3].toString(), modifierList)) { - case 0: // default; - newFormat = NumberFormat.getInstance(locale); - break; - case 1: case 2:// currency - newFormat = NumberFormat.getCurrencyInstance(locale); - break; - case 3: case 4:// percent - newFormat = NumberFormat.getPercentInstance(locale); - break; - case 5: case 6:// integer - newFormat = NumberFormat.getIntegerInstance(locale); - break; - default: // pattern - newFormat = new DecimalFormat(segments[3].toString(), DecimalFormatSymbols.getInstance(locale)); - break; - } - break; - case 3: case 4: // date - switch (findKeyword(segments[3].toString(), dateModifierList)) { - case 0: // default - newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale); - break; - case 1: case 2: // short - newFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale); + if (segments[SEG_TYPE].length() != 0) { + int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS); + switch (type) { + case TYPE_NULL: + // Type "" is allowed. e.g., "{0,}", "{0,,}", and "{0,,#}" + // are treated as "{0}". break; - case 3: case 4: // medium - newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale); - break; - case 5: case 6: // long - newFormat = DateFormat.getDateInstance(DateFormat.LONG, locale); - break; - case 7: case 8: // full - newFormat = DateFormat.getDateInstance(DateFormat.FULL, locale); - break; - default: - newFormat = new SimpleDateFormat(segments[3].toString(), locale); - break; - } - break; - case 5: case 6:// time - switch (findKeyword(segments[3].toString(), dateModifierList)) { - case 0: // default - newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale); - break; - case 1: case 2: // short - newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale); - break; - case 3: case 4: // medium - newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale); + + case TYPE_NUMBER: + switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) { + case MODIFIER_DEFAULT: + newFormat = NumberFormat.getInstance(locale); + break; + case MODIFIER_CURRENCY: + newFormat = NumberFormat.getCurrencyInstance(locale); + break; + case MODIFIER_PERCENT: + newFormat = NumberFormat.getPercentInstance(locale); + break; + case MODIFIER_INTEGER: + newFormat = NumberFormat.getIntegerInstance(locale); + break; + default: // DecimalFormat pattern + try { + newFormat = new DecimalFormat(segments[SEG_MODIFIER], + DecimalFormatSymbols.getInstance(locale)); + } catch (IllegalArgumentException e) { + maxOffset = oldMaxOffset; + throw e; + } + break; + } break; - case 5: case 6: // long - newFormat = DateFormat.getTimeInstance(DateFormat.LONG, locale); + + case TYPE_DATE: + case TYPE_TIME: + int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS); + if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) { + if (type == TYPE_DATE) { + newFormat = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[mod], + locale); + } else { + newFormat = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[mod], + locale); + } + } else { + // SimpleDateFormat pattern + try { + newFormat = new SimpleDateFormat(segments[SEG_MODIFIER], locale); + } catch (IllegalArgumentException e) { + maxOffset = oldMaxOffset; + throw e; + } + } break; - case 7: case 8: // full - newFormat = DateFormat.getTimeInstance(DateFormat.FULL, locale); + + case TYPE_CHOICE: + try { + // ChoiceFormat pattern + newFormat = new ChoiceFormat(segments[SEG_MODIFIER]); + } catch (Exception e) { + maxOffset = oldMaxOffset; + throw new IllegalArgumentException("Choice Pattern incorrect: " + + segments[SEG_MODIFIER], e); + } break; + default: - newFormat = new SimpleDateFormat(segments[3].toString(), locale); - break; - } - break; - case 7: case 8:// choice - try { - newFormat = new ChoiceFormat(segments[3].toString()); - } catch (Exception e) { maxOffset = oldMaxOffset; - throw new IllegalArgumentException( - "Choice Pattern incorrect"); + throw new IllegalArgumentException("unknown format type: " + + segments[SEG_TYPE]); } - break; - default: - maxOffset = oldMaxOffset; - throw new IllegalArgumentException("unknown format type: " + - segments[2].toString()); } formats[offsetNumber] = newFormat; - segments[1].setLength(0); // throw away other segments - segments[2].setLength(0); - segments[3].setLength(0); } private static final int findKeyword(String s, String[] list) { - s = s.trim().toLowerCase(); for (int i = 0; i < list.length; ++i) { if (s.equals(list[i])) return i; } + + // Try trimmed lowercase. + String ls = s.trim().toLowerCase(Locale.ROOT); + if (ls != s) { + for (int i = 0; i < list.length; ++i) { + if (ls.equals(list[i])) + return i; + } + } return -1; } - private static final void copyAndFixQuotes( - String source, int start, int end, StringBuffer target) { + private static final void copyAndFixQuotes(String source, int start, int end, + StringBuilder target) { + boolean quoted = false; + for (int i = start; i < end; ++i) { char ch = source.charAt(i); if (ch == '{') { - target.append("'{'"); - } else if (ch == '}') { - target.append("'}'"); + if (!quoted) { + target.append('\''); + quoted = true; + } + target.append(ch); } else if (ch == '\'') { target.append("''"); } else { + if (quoted) { + target.append('\''); + quoted = false; + } target.append(ch); } } + if (quoted) { + target.append('\''); + } } /** diff --git a/test/java/text/Format/MessageFormat/Bug7003643.java b/test/java/text/Format/MessageFormat/Bug7003643.java new file mode 100644 index 0000000000000000000000000000000000000000..aeb722cac17203193811133c8b0ece4dc7d19528 --- /dev/null +++ b/test/java/text/Format/MessageFormat/Bug7003643.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 7003643 + * @summary Make sure MessageFormat.toPattern produces correct quoting. (SPI part is tested in PluggableLocale tests.) + */ + +import java.text.*; +import java.util.*; + +public class Bug7003643 { + private static final int N = 5; + + private static final String[] elements = { + "'{'", "'{", "{", "''", "}", "a", "'", + }; + + public static void main(String[] args) { + Random rand = new Random(); + int count = 0; + int max = (int) (Math.pow((double)elements.length, (double)N)/0.52); + while (count < max) { + // Create a random pattern. If the produced pattern is + // valid, then proceed with the round-trip testing. + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < N; i++) { + sb.append(elements[rand.nextInt(elements.length)]); + } + String pattern = sb.toString(); + MessageFormat mf = null; + try { + mf = new MessageFormat(pattern); + } catch (IllegalArgumentException e) { + // bad pattern data + } + if (mf == null) { + continue; + } + count++; + String res1 = MessageFormat.format(pattern, 123); + String toPattern = mf.toPattern(); + String res2 = MessageFormat.format(toPattern, 123); + if (!res1.equals(res2)) { + String s = String.format("Failed%n pattern=\"%s\" => result=\"%s\"%n" + + " toPattern()=\"%s\" => result=\"%s\"%n", + pattern, res1, toPattern, res2); + throw new RuntimeException(s); + } + } + } +} diff --git a/test/java/util/PluggableLocale/DateFormatProviderTest.java b/test/java/util/PluggableLocale/DateFormatProviderTest.java index 6112b6836a437472d97d0f8844631730315e8479..6d5ecd23f3a2e340e48bfa8b4812f30e69a5e02f 100644 --- a/test/java/util/PluggableLocale/DateFormatProviderTest.java +++ b/test/java/util/PluggableLocale/DateFormatProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,6 +44,7 @@ public class DateFormatProviderTest extends ProviderTest { availableLocalesTest(); objectValidityTest(); extendedVariantTest(); + messageFormatTest(); } void availableLocalesTest() { @@ -118,4 +119,48 @@ public class DateFormatProviderTest extends ProviderTest { } } } + + + private static final String[] TYPES = { + "date", + "time" + }; + private static final String[] MODIFIERS = { + "", + "short", + "medium", // Same as DEFAULT + "long", + "full" + }; + + void messageFormatTest() { + for (Locale target : providerloc) { + for (String type : TYPES) { + for (String modifier : MODIFIERS) { + String pattern, expected; + if (modifier.equals("")) { + pattern = String.format("%s={0,%s}", type, type); + } else { + pattern = String.format("%s={0,%s,%s}", type, type, modifier); + } + if (modifier.equals("medium")) { + // medium is default. + expected = String.format("%s={0,%s}", type, type); + } else { + expected = pattern; + } + MessageFormat mf = new MessageFormat(pattern, target); + Format[] fmts = mf.getFormats(); + if (fmts[0] instanceof SimpleDateFormat) { + continue; + } + String toPattern = mf.toPattern(); + if (!toPattern.equals(expected)) { + throw new RuntimeException("messageFormatTest: got '" + toPattern + + "', expected '" + expected + "'"); + } + } + } + } + } } diff --git a/test/java/util/PluggableLocale/DateFormatProviderTest.sh b/test/java/util/PluggableLocale/DateFormatProviderTest.sh index 0d7ad953fe209af41be14765e50cf35a94cf5954..228a2484a100b09bd22da771dad20e0add0f10d9 100644 --- a/test/java/util/PluggableLocale/DateFormatProviderTest.sh +++ b/test/java/util/PluggableLocale/DateFormatProviderTest.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,6 @@ #!/bin/sh # # @test -# @bug 4052440 +# @bug 4052440 7003643 # @summary DateFormatProvider tests # @run shell ExecTest.sh foo DateFormatProviderTest true diff --git a/test/java/util/PluggableLocale/NumberFormatProviderTest.java b/test/java/util/PluggableLocale/NumberFormatProviderTest.java index 93540bf1764da7281305d1b3702098b2181200b4..a7ebfb794975b15b3e95d350911244e9ebde2be0 100644 --- a/test/java/util/PluggableLocale/NumberFormatProviderTest.java +++ b/test/java/util/PluggableLocale/NumberFormatProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,8 @@ import java.util.*; import sun.util.*; import sun.util.resources.*; +import com.foo.FooNumberFormat; + public class NumberFormatProviderTest extends ProviderTest { com.foo.NumberFormatProviderImpl nfp = new com.foo.NumberFormatProviderImpl(); @@ -43,6 +45,7 @@ public class NumberFormatProviderTest extends ProviderTest { NumberFormatProviderTest() { availableLocalesTest(); objectValidityTest(); + messageFormatTest(); } void availableLocalesTest() { @@ -72,14 +75,10 @@ public class NumberFormatProviderTest extends ProviderTest { } // result object - String resultCur = - ((DecimalFormat)NumberFormat.getCurrencyInstance(target)).toPattern(); - String resultInt = - ((DecimalFormat)NumberFormat.getIntegerInstance(target)).toPattern(); - String resultNum = - ((DecimalFormat)NumberFormat.getNumberInstance(target)).toPattern(); - String resultPer = - ((DecimalFormat)NumberFormat.getPercentInstance(target)).toPattern(); + String resultCur = getPattern(NumberFormat.getCurrencyInstance(target)); + String resultInt = getPattern(NumberFormat.getIntegerInstance(target)); + String resultNum = getPattern(NumberFormat.getNumberInstance(target)); + String resultPer = getPattern(NumberFormat.getPercentInstance(target)); // provider's object (if any) String providersCur = null; @@ -87,21 +86,21 @@ public class NumberFormatProviderTest extends ProviderTest { String providersNum = null; String providersPer = null; if (providerloc.contains(target)) { - DecimalFormat dfCur = (DecimalFormat)nfp.getCurrencyInstance(target); + NumberFormat dfCur = nfp.getCurrencyInstance(target); if (dfCur != null) { - providersCur = dfCur.toPattern(); + providersCur = getPattern(dfCur); } - DecimalFormat dfInt = (DecimalFormat)nfp.getIntegerInstance(target); + NumberFormat dfInt = nfp.getIntegerInstance(target); if (dfInt != null) { - providersInt = dfInt.toPattern(); + providersInt = getPattern(dfInt); } - DecimalFormat dfNum = (DecimalFormat)nfp.getNumberInstance(target); + NumberFormat dfNum = nfp.getNumberInstance(target); if (dfNum != null) { - providersNum = dfNum.toPattern(); + providersNum = getPattern(dfNum); } - DecimalFormat dfPer = (DecimalFormat)nfp.getPercentInstance(target); + NumberFormat dfPer = nfp.getPercentInstance(target); if (dfPer != null) { - providersPer = dfPer.toPattern(); + providersPer = getPattern(dfPer); } } @@ -174,4 +173,35 @@ public class NumberFormatProviderTest extends ProviderTest { } } } + + private static String getPattern(NumberFormat nf) { + if (nf instanceof DecimalFormat) { + return ((DecimalFormat)nf).toPattern(); + } + if (nf instanceof FooNumberFormat) { + return ((FooNumberFormat)nf).toPattern(); + } + return null; + } + + private static final String[] NUMBER_PATTERNS = { + "num={0,number}", + "num={0,number,currency}", + "num={0,number,percent}", + "num={0,number,integer}" + }; + + void messageFormatTest() { + for (Locale target : providerloc) { + for (String pattern : NUMBER_PATTERNS) { + MessageFormat mf = new MessageFormat(pattern, target); + String toPattern = mf.toPattern(); + if (!pattern.equals(toPattern)) { + throw new RuntimeException("MessageFormat.toPattern: got '" + + toPattern + + "', expected '" + pattern + "'"); + } + } + } + } } diff --git a/test/java/util/PluggableLocale/NumberFormatProviderTest.sh b/test/java/util/PluggableLocale/NumberFormatProviderTest.sh index 7967da9f070d1f4a9bd66a7bee05e8cc983a7c4f..7f4e902a91451bb0bf5daca113600d355109f14b 100644 --- a/test/java/util/PluggableLocale/NumberFormatProviderTest.sh +++ b/test/java/util/PluggableLocale/NumberFormatProviderTest.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,6 @@ #!/bin/sh # # @test -# @bug 4052440 +# @bug 4052440 7003643 # @summary NumberFormatProvider tests # @run shell ExecTest.sh foo NumberFormatProviderTest true diff --git a/test/java/util/PluggableLocale/fooprovider.jar b/test/java/util/PluggableLocale/fooprovider.jar index 29ea53fcca62228087b1f864009aa21447af46c2..c1b5723f9b95dad064ed0393dd878cd7d82f3c51 100644 Binary files a/test/java/util/PluggableLocale/fooprovider.jar and b/test/java/util/PluggableLocale/fooprovider.jar differ diff --git a/test/java/util/PluggableLocale/providersrc/DateFormatProviderImpl.java b/test/java/util/PluggableLocale/providersrc/DateFormatProviderImpl.java index 25e7b1769462d8df3c650854e588339e94797bb5..55bc8d5ae73f3d1c8cb9a7b40b7acc078c71ef51 100644 --- a/test/java/util/PluggableLocale/providersrc/DateFormatProviderImpl.java +++ b/test/java/util/PluggableLocale/providersrc/DateFormatProviderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,7 +42,7 @@ public class DateFormatProviderImpl extends DateFormatProvider { static String[] datePattern = { "yyyy'\u5e74'M'\u6708'd'\u65e5'", // full date pattern - "yyyy/MM/dd", // long date pattern + "yyyy/MMM/dd", // long date pattern "yyyy/MM/dd", // medium date pattern "yy/MM/dd" // short date pattern }; @@ -68,7 +68,7 @@ public class DateFormatProviderImpl extends DateFormatProvider { public DateFormat getDateInstance(int style, Locale locale) { for (int i = 0; i < avail.length; i ++) { if (Utils.supportsLocale(avail[i], locale)) { - return new SimpleDateFormat(datePattern[style]+dialect[i], locale); + return new FooDateFormat(datePattern[style]+dialect[i], locale); } } throw new IllegalArgumentException("locale is not supported: "+locale); @@ -77,7 +77,7 @@ public class DateFormatProviderImpl extends DateFormatProvider { public DateFormat getTimeInstance(int style, Locale locale) { for (int i = 0; i < avail.length; i ++) { if (Utils.supportsLocale(avail[i], locale)) { - return new SimpleDateFormat(timePattern[style]+dialect[i], locale); + return new FooDateFormat(timePattern[style]+dialect[i], locale); } } throw new IllegalArgumentException("locale is not supported: "+locale); @@ -86,7 +86,7 @@ public class DateFormatProviderImpl extends DateFormatProvider { public DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale) { for (int i = 0; i < avail.length; i ++) { if (Utils.supportsLocale(avail[i], locale)) { - return new SimpleDateFormat( + return new FooDateFormat( datePattern[dateStyle]+" "+timePattern[timeStyle]+dialect[i], locale); } } diff --git a/test/java/util/PluggableLocale/providersrc/FooDateFormat.java b/test/java/util/PluggableLocale/providersrc/FooDateFormat.java new file mode 100644 index 0000000000000000000000000000000000000000..80d4ce2010fb6555b8ea6042f3a7b0dcd094d11f --- /dev/null +++ b/test/java/util/PluggableLocale/providersrc/FooDateFormat.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.foo; + +import java.text.*; +import java.util.*; + +/** + * FooDateFormat provides SimpleDateFormat methods required for the SPI testing. + */ +public class FooDateFormat extends DateFormat { + private SimpleDateFormat sdf; + + public FooDateFormat(String pattern, Locale loc) { + sdf = new SimpleDateFormat(pattern, loc); + } + + @Override + public StringBuffer format(Date date, + StringBuffer toAppendTo, + FieldPosition fieldPosition) { + return sdf.format(date, toAppendTo, fieldPosition); + } + + @Override + public Date parse(String source, ParsePosition pos) { + return sdf.parse(source, pos); + } + + @Override + public boolean equals(Object other) { + return other instanceof FooDateFormat + && sdf.equals(((FooDateFormat)other).sdf); + } + + @Override + public int hashCode() { + return sdf.hashCode(); + } +} diff --git a/test/java/util/PluggableLocale/providersrc/FooNumberFormat.java b/test/java/util/PluggableLocale/providersrc/FooNumberFormat.java new file mode 100644 index 0000000000000000000000000000000000000000..d13bd7362fed3bb046ae3053479d971d1ec3f076 --- /dev/null +++ b/test/java/util/PluggableLocale/providersrc/FooNumberFormat.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.foo; + +import java.text.*; + +/** + * FooNumberFormat provides DecimalFormat methods required for the SPI testing. + */ +public class FooNumberFormat extends NumberFormat { + private DecimalFormat df; + + public FooNumberFormat(String pattern, DecimalFormatSymbols dfs) { + df = new DecimalFormat(pattern, dfs); + } + + @Override + public StringBuffer format(double number, + StringBuffer toAppendTo, + FieldPosition pos) { + return df.format(number, toAppendTo, pos); + } + + @Override + public StringBuffer format(long number, + StringBuffer toAppendTo, + FieldPosition pos) { + return df.format(number, toAppendTo, pos); + } + + @Override + public Number parse(String source, ParsePosition parsePosition) { + return df.parse(source, parsePosition); + } + + @Override + public boolean equals(Object other) { + return other instanceof FooNumberFormat + && df.equals(((FooNumberFormat)other).df); + } + + @Override + public int hashCode() { + return df.hashCode(); + } + + // DecimalFormat specific methods required for testing + + public String toPattern() { + return df.toPattern(); + } + + public DecimalFormatSymbols getDecimalFormatSymbols() { + return df.getDecimalFormatSymbols(); + } + + public void setDecimalSeparatorAlwaysShown(boolean newValue) { + df.setDecimalSeparatorAlwaysShown(newValue); + } +} diff --git a/test/java/util/PluggableLocale/providersrc/Makefile b/test/java/util/PluggableLocale/providersrc/Makefile index a85ea065e981950aa2e5b8a3f872657f4f23beaa..8d2fa9cab808568a238929fef8847c7bc926c73b 100644 --- a/test/java/util/PluggableLocale/providersrc/Makefile +++ b/test/java/util/PluggableLocale/providersrc/Makefile @@ -28,6 +28,8 @@ FOOFILES_JAVA = \ DateFormatSymbolsProviderImpl.java \ DecimalFormatSymbolsProviderImpl.java \ NumberFormatProviderImpl.java \ + FooDateFormat.java \ + FooNumberFormat.java \ Utils.java BARFILES_JAVA = \ diff --git a/test/java/util/PluggableLocale/providersrc/NumberFormatProviderImpl.java b/test/java/util/PluggableLocale/providersrc/NumberFormatProviderImpl.java index 164de5e1db634982482a24c2e1f9697e4fd49d73..9aa677cf89bcaf004b3c1fd9c4a12c536a4d3d9a 100644 --- a/test/java/util/PluggableLocale/providersrc/NumberFormatProviderImpl.java +++ b/test/java/util/PluggableLocale/providersrc/NumberFormatProviderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,13 +49,15 @@ public class NumberFormatProviderImpl extends NumberFormatProvider { static String[] patterns = { "#,##0.###{0};-#,##0.###{1}", // decimal pattern + "#{0};(#){1}", // integer pattern "\u00A4#,##0{0};-\u00A4#,##0{1}", // currency pattern "#,##0%{0}" // percent pattern }; // Constants used by factory methods to specify a style of format. static final int NUMBERSTYLE = 0; - static final int CURRENCYSTYLE = 1; - static final int PERCENTSTYLE = 2; + static final int INTEGERSTYLE = 1; + static final int CURRENCYSTYLE = 2; + static final int PERCENTSTYLE = 3; public Locale[] getAvailableLocales() { return avail; @@ -68,10 +70,10 @@ public class NumberFormatProviderImpl extends NumberFormatProvider { MessageFormat.format(patterns[CURRENCYSTYLE], dialect[i], dialect[i]); - DecimalFormat df = new DecimalFormat(pattern, + FooNumberFormat nf = new FooNumberFormat(pattern, DecimalFormatSymbols.getInstance(locale)); - adjustForCurrencyDefaultFractionDigits(df); - return df; + adjustForCurrencyDefaultFractionDigits(nf); + return nf; } } throw new IllegalArgumentException("locale is not supported: "+locale); @@ -81,15 +83,15 @@ public class NumberFormatProviderImpl extends NumberFormatProvider { for (int i = 0; i < avail.length; i ++) { if (Utils.supportsLocale(avail[i], locale)) { String pattern = - MessageFormat.format(patterns[NUMBERSTYLE], + MessageFormat.format(patterns[INTEGERSTYLE], dialect[i], dialect[i]); - DecimalFormat df = new DecimalFormat(pattern, + FooNumberFormat nf = new FooNumberFormat(pattern, DecimalFormatSymbols.getInstance(locale)); - df.setMaximumFractionDigits(0); - df.setDecimalSeparatorAlwaysShown(false); - df.setParseIntegerOnly(true); - return df; + nf.setMaximumFractionDigits(0); + nf.setDecimalSeparatorAlwaysShown(false); + nf.setParseIntegerOnly(true); + return nf; } } throw new IllegalArgumentException("locale is not supported: "+locale); @@ -102,7 +104,7 @@ public class NumberFormatProviderImpl extends NumberFormatProvider { MessageFormat.format(patterns[NUMBERSTYLE], dialect[i], dialect[i]); - return new DecimalFormat(pattern, + return new FooNumberFormat(pattern, DecimalFormatSymbols.getInstance(locale)); } } @@ -115,7 +117,7 @@ public class NumberFormatProviderImpl extends NumberFormatProvider { String pattern = MessageFormat.format(patterns[PERCENTSTYLE], dialect[i]); - return new DecimalFormat(pattern, + return new FooNumberFormat(pattern, DecimalFormatSymbols.getInstance(locale)); } } @@ -126,8 +128,8 @@ public class NumberFormatProviderImpl extends NumberFormatProvider { * Adjusts the minimum and maximum fraction digits to values that * are reasonable for the currency's default fraction digits. */ - void adjustForCurrencyDefaultFractionDigits(DecimalFormat df) { - DecimalFormatSymbols dfs = df.getDecimalFormatSymbols(); + void adjustForCurrencyDefaultFractionDigits(FooNumberFormat nf) { + DecimalFormatSymbols dfs = nf.getDecimalFormatSymbols(); Currency currency = dfs.getCurrency(); if (currency == null) { try { @@ -138,15 +140,15 @@ public class NumberFormatProviderImpl extends NumberFormatProvider { if (currency != null) { int digits = currency.getDefaultFractionDigits(); if (digits != -1) { - int oldMinDigits = df.getMinimumFractionDigits(); + int oldMinDigits = nf.getMinimumFractionDigits(); // Common patterns are "#.##", "#.00", "#". // Try to adjust all of them in a reasonable way. - if (oldMinDigits == df.getMaximumFractionDigits()) { - df.setMinimumFractionDigits(digits); - df.setMaximumFractionDigits(digits); + if (oldMinDigits == nf.getMaximumFractionDigits()) { + nf.setMinimumFractionDigits(digits); + nf.setMaximumFractionDigits(digits); } else { - df.setMinimumFractionDigits(Math.min(digits, oldMinDigits)); - df.setMaximumFractionDigits(digits); + nf.setMinimumFractionDigits(Math.min(digits, oldMinDigits)); + nf.setMaximumFractionDigits(digits); } } }