changeset 3307:94e0438c74c3

7003643: [Fmt-Me] MessageFormat.toPattern produces wrong quoted string and subformat modifiers 7008195: [Fmt-Me] Improve MessageFormat.applyPattern performance Reviewed-by: naoto, peytoia
author okutsu
date Mon, 27 Dec 2010 14:13:52 +0900
parents 69b2e5838eee
children c06d4327c0f2
files src/share/classes/java/text/MessageFormat.java test/java/text/Format/MessageFormat/Bug7003643.java test/java/util/PluggableLocale/DateFormatProviderTest.java test/java/util/PluggableLocale/DateFormatProviderTest.sh test/java/util/PluggableLocale/NumberFormatProviderTest.java test/java/util/PluggableLocale/NumberFormatProviderTest.sh test/java/util/PluggableLocale/fooprovider.jar test/java/util/PluggableLocale/providersrc/DateFormatProviderImpl.java test/java/util/PluggableLocale/providersrc/FooDateFormat.java test/java/util/PluggableLocale/providersrc/FooNumberFormat.java test/java/util/PluggableLocale/providersrc/Makefile test/java/util/PluggableLocale/providersrc/NumberFormatProviderImpl.java
diffstat 12 files changed, 604 insertions(+), 231 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/text/MessageFormat.java	Thu Dec 23 18:25:35 2010 +0300
+++ b/src/share/classes/java/text/MessageFormat.java	Mon Dec 27 14:13:52 2010 +0900
@@ -423,18 +423,19 @@
      * @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 @@
                             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 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;
+                    }
                 }
-            } else if (formats[i] instanceof ChoiceFormat) {
-                result.append(",choice,"
-                              + ((ChoiceFormat)formats[i]).toPattern());
+                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 {
                 //result.append(", unknown");
             }
@@ -674,7 +685,7 @@
      *
      * @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 @@
                 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 @@
             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 @@
         }
     }
 
-    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 @@
         }
         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);
+        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 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));
+
+            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;
-            }
-            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);
+
+            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 3: case 4: // medium
-                newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, 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;
-            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;
+                maxOffset = oldMaxOffset;
+                throw new IllegalArgumentException("unknown format type: " +
+                                                   segments[SEG_TYPE]);
             }
-            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);
-                break;
-            case 5: case 6: // long
-                newFormat = DateFormat.getTimeInstance(DateFormat.LONG, locale);
-                break;
-            case 7: case 8: // full
-                newFormat = DateFormat.getTimeInstance(DateFormat.FULL, locale);
-                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");
-            }
-            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('\'');
+        }
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/text/Format/MessageFormat/Bug7003643.java	Mon Dec 27 14:13:52 2010 +0900
@@ -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);
+            }
+        }
+    }
+}
--- a/test/java/util/PluggableLocale/DateFormatProviderTest.java	Thu Dec 23 18:25:35 2010 +0300
+++ b/test/java/util/PluggableLocale/DateFormatProviderTest.java	Mon Dec 27 14:13:52 2010 +0900
@@ -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 @@
         availableLocalesTest();
         objectValidityTest();
         extendedVariantTest();
+        messageFormatTest();
     }
 
     void availableLocalesTest() {
@@ -118,4 +119,48 @@
             }
         }
     }
+
+
+    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 + "'");
+                    }
+                }
+            }
+        }
+    }
 }
--- a/test/java/util/PluggableLocale/DateFormatProviderTest.sh	Thu Dec 23 18:25:35 2010 +0300
+++ b/test/java/util/PluggableLocale/DateFormatProviderTest.sh	Mon Dec 27 14:13:52 2010 +0900
@@ -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
--- a/test/java/util/PluggableLocale/NumberFormatProviderTest.java	Thu Dec 23 18:25:35 2010 +0300
+++ b/test/java/util/PluggableLocale/NumberFormatProviderTest.java	Mon Dec 27 14:13:52 2010 +0900
@@ -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 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 @@
     NumberFormatProviderTest() {
         availableLocalesTest();
         objectValidityTest();
+        messageFormatTest();
     }
 
     void availableLocalesTest() {
@@ -72,14 +75,10 @@
             }
 
             // 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 @@
             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 @@
             }
         }
     }
+
+    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 + "'");
+                }
+            }
+        }
+    }
 }
--- a/test/java/util/PluggableLocale/NumberFormatProviderTest.sh	Thu Dec 23 18:25:35 2010 +0300
+++ b/test/java/util/PluggableLocale/NumberFormatProviderTest.sh	Mon Dec 27 14:13:52 2010 +0900
@@ -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
Binary file test/java/util/PluggableLocale/fooprovider.jar has changed
--- a/test/java/util/PluggableLocale/providersrc/DateFormatProviderImpl.java	Thu Dec 23 18:25:35 2010 +0300
+++ b/test/java/util/PluggableLocale/providersrc/DateFormatProviderImpl.java	Mon Dec 27 14:13:52 2010 +0900
@@ -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 @@
 
     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 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 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 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);
             }
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/util/PluggableLocale/providersrc/FooDateFormat.java	Mon Dec 27 14:13:52 2010 +0900
@@ -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();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/util/PluggableLocale/providersrc/FooNumberFormat.java	Mon Dec 27 14:13:52 2010 +0900
@@ -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);
+    }
+}
--- a/test/java/util/PluggableLocale/providersrc/Makefile	Thu Dec 23 18:25:35 2010 +0300
+++ b/test/java/util/PluggableLocale/providersrc/Makefile	Mon Dec 27 14:13:52 2010 +0900
@@ -28,6 +28,8 @@
     DateFormatSymbolsProviderImpl.java \
     DecimalFormatSymbolsProviderImpl.java \
     NumberFormatProviderImpl.java \
+    FooDateFormat.java \
+    FooNumberFormat.java \
     Utils.java
 
 BARFILES_JAVA = \
--- a/test/java/util/PluggableLocale/providersrc/NumberFormatProviderImpl.java	Thu Dec 23 18:25:35 2010 +0300
+++ b/test/java/util/PluggableLocale/providersrc/NumberFormatProviderImpl.java	Mon Dec 27 14:13:52 2010 +0900
@@ -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 @@
 
     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 @@
                     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 @@
         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 @@
                     MessageFormat.format(patterns[NUMBERSTYLE],
                                          dialect[i],
                                          dialect[i]);
-                return new DecimalFormat(pattern,
+                return new FooNumberFormat(pattern,
                     DecimalFormatSymbols.getInstance(locale));
             }
         }
@@ -115,7 +117,7 @@
                 String pattern =
                     MessageFormat.format(patterns[PERCENTSTYLE],
                                          dialect[i]);
-                return new DecimalFormat(pattern,
+                return new FooNumberFormat(pattern,
                     DecimalFormatSymbols.getInstance(locale));
             }
         }
@@ -126,8 +128,8 @@
      * 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 @@
         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);
                 }
             }
         }