Fix Formatter.format's handling of Formattable.

This doesn't make much difference to performance, but it is
slightly faster and I think it reads better too. Proof of the
latter is the fact that the rewritten form accidentally fixed
bug 1767: the old code was storing up literal text until after
handling the next format specifier, which is wrong if the
format specifier has side effects caused by the use of
Formattable.

(I don't plan on doing any more on the performance bug for now,
though I note that %g allocates and manipulates BigDecimal
instances, which would be worth looking at when we get round to
the bug that causes it to fail a harmony test.)

Bug: 1476, 1767
diff --git a/libcore/luni/src/main/java/java/util/Formatter.java b/libcore/luni/src/main/java/java/util/Formatter.java
index 3196f0e..3d8b94e 100644
--- a/libcore/luni/src/main/java/java/util/Formatter.java
+++ b/libcore/luni/src/main/java/java/util/Formatter.java
@@ -885,7 +885,6 @@
         if (transformer == null || ! transformer.locale.equals(l)) {
             transformer = new Transformer(this, l);
         }
-        // END android-changed
 
         int currentObjectIndex = 0;
         Object lastArgument = null;
@@ -893,12 +892,13 @@
         while (formatBuffer.hasRemaining()) {
             parser.reset();
             FormatToken token = parser.getNextFormatToken();
-            String result;
             String plainText = token.getPlainText();
             if (token.getConversionType() == (char) FormatToken.UNSET) {
-                result = plainText;
+                outputCharSequence(plainText);
             } else {
                 plainText = plainText.substring(0, plainText.indexOf('%'));
+                outputCharSequence(plainText);
+
                 Object argument = null;
                 if (token.requireArgument()) {
                     int index = token.getArgIndex() == FormatToken.UNSET ? currentObjectIndex++
@@ -908,21 +908,26 @@
                     lastArgument = argument;
                     hasLastArgumentSet = true;
                 }
-                result = transformer.transform(token, argument);
-                result = (null == result ? plainText : plainText + result);
-            }
-            // if output is made by formattable callback
-            if (null != result) {
-                try {
-                    out.append(result);
-                } catch (IOException e) {
-                    lastIOException = e;
-                }
+                outputCharSequence(transformer.transform(token, argument));
             }
         }
+        // END android-changed
         return this;
     }
 
+    // BEGIN android-added
+    // Fixes http://code.google.com/p/android/issues/detail?id=1767.
+    private void outputCharSequence(CharSequence cs) {
+        if (cs != null) {
+            try {
+                out.append(cs);
+            } catch (IOException e) {
+                lastIOException = e;
+            }
+        }
+    }
+    // END android-added
+
     private Object getArgument(Object[] args, int index, FormatToken token,
             Object lastArgument, boolean hasLastArgumentSet) {
         if (index == FormatToken.LAST_ARGUMENT_INDEX && !hasLastArgumentSet) {
@@ -1184,13 +1189,13 @@
          * Gets the formatted string according to the format token and the
          * argument.
          */
-        String transform(FormatToken token, Object argument) {
+        CharSequence transform(FormatToken token, Object argument) {
 
             /* init data member to print */
             this.formatToken = token;
             this.arg = argument;
 
-            String result;
+            CharSequence result;
             switch (token.getConversionType()) {
                 case 'B':
                 case 'b': {
@@ -1254,7 +1259,7 @@
 
             if (Character.isUpperCase(token.getConversionType())) {
                 if (null != result) {
-                    result = result.toUpperCase(Locale.US);
+                    result = result.toString().toUpperCase(Locale.US);
                 }
             }
             return result;
@@ -1263,7 +1268,7 @@
         /*
          * Transforms the Boolean argument to a formatted string.
          */
-        private String transformFromBoolean() {
+        private CharSequence transformFromBoolean() {
             StringBuilder result = new StringBuilder();
             int startIndex = 0;
             int flags = formatToken.getFlags();
@@ -1294,7 +1299,7 @@
         /*
          * Transforms the hashcode of the argument to a formatted string.
          */
-        private String transformFromHashCode() {
+        private CharSequence transformFromHashCode() {
             StringBuilder result = new StringBuilder();
 
             int startIndex = 0;
@@ -1324,7 +1329,7 @@
         /*
          * Transforms the String to a formatted string.
          */
-        private String transformFromString() {
+        private CharSequence transformFromString() {
             StringBuilder result = new StringBuilder();
             int startIndex = 0;
             int flags = formatToken.getFlags();
@@ -1374,7 +1379,7 @@
         /*
          * Transforms the Character to a formatted string.
          */
-        private String transformFromCharacter() {
+        private CharSequence transformFromCharacter() {
             StringBuilder result = new StringBuilder();
 
             int startIndex = 0;
@@ -1434,7 +1439,7 @@
          * Transforms percent to a formatted string. Only '-' is legal flag.
          * Precision is illegal.
          */
-        private String transformFromPercent() {
+        private CharSequence transformFromPercent() {
             StringBuilder result = new StringBuilder("%"); //$NON-NLS-1$
 
             int startIndex = 0;
@@ -1462,7 +1467,7 @@
          * Transforms line separator to a formatted string. Any flag, the width
          * or the precision is illegal.
          */
-        private String transformFromLineSeparator() {
+        private CharSequence transformFromLineSeparator() {
             if (formatToken.isPrecisionSet()) {
                 throw new IllegalFormatPrecisionException(formatToken
                         .getPrecision());
@@ -1492,7 +1497,7 @@
         /*
          * Pads characters to the formatted string.
          */
-        private String padding(StringBuilder source, int startIndex) {
+        private CharSequence padding(StringBuilder source, int startIndex) {
             int start = startIndex;
             boolean paddingRight = formatToken
                     .isFlagSet(FormatToken.FLAG_MINUS);
@@ -1520,7 +1525,7 @@
                 width = Math.max(source.length(), width);
             }
             if (length >= width) {
-                return source.toString();
+                return source;
             }
 
             char[] paddings = new char[width - length];
@@ -1532,13 +1537,13 @@
             } else {
                 source.insert(start, insertString);
             }
-            return source.toString();
+            return source;
         }
 
         /*
          * Transforms the Integer to a formatted string.
          */
-        private String transformFromInteger() {
+        private CharSequence transformFromInteger() {
             int startIndex = 0;
             boolean isNegative = false;
             StringBuilder result = new StringBuilder();
@@ -1651,7 +1656,7 @@
             if (isNegative
                     && formatToken.isFlagSet(FormatToken.FLAG_PARENTHESIS)) {
                 result = wrapParentheses(result);
-                return result.toString();
+                return result;
 
             }
             if (isNegative && formatToken.isFlagSet(FormatToken.FLAG_ZERO)) {
@@ -1680,7 +1685,7 @@
             return result;
         }
 
-        private String transformFromSpecialNumber() {
+        private CharSequence transformFromSpecialNumber() {
             String source = null;
 
             if (!(arg instanceof Number) || arg instanceof BigDecimal) {
@@ -1713,12 +1718,12 @@
                 formatToken.setPrecision(FormatToken.UNSET);
                 formatToken.setFlags(formatToken.getFlags()
                         & (~FormatToken.FLAG_ZERO));
-                source = padding(new StringBuilder(source), 0);
+                return padding(new StringBuilder(source), 0);
             }
             return source;
         }
 
-        private String transformFromNull() {
+        private CharSequence transformFromNull() {
             formatToken.setFlags(formatToken.getFlags()
                     & (~FormatToken.FLAG_ZERO));
             return padding(new StringBuilder("null"), 0); //$NON-NLS-1$
@@ -1727,7 +1732,7 @@
         /*
          * Transforms a BigInteger to a formatted string.
          */
-        private String transformFromBigInteger() {
+        private CharSequence transformFromBigInteger() {
             int startIndex = 0;
             boolean isNegative = false;
             StringBuilder result = new StringBuilder();
@@ -1817,7 +1822,7 @@
             if (isNegative
                     && formatToken.isFlagSet(FormatToken.FLAG_PARENTHESIS)) {
                 result = wrapParentheses(result);
-                return result.toString();
+                return result;
 
             }
             if (isNegative && formatToken.isFlagSet(FormatToken.FLAG_ZERO)) {
@@ -1829,7 +1834,7 @@
         /*
          * Transforms a Float,Double or BigDecimal to a formatted string.
          */
-        private String transformFromFloat() {
+        private CharSequence transformFromFloat() {
             StringBuilder result = new StringBuilder();
             int startIndex = 0;
             char currentConversionType = formatToken.getConversionType();
@@ -1883,7 +1888,7 @@
                         currentConversionType, arg.getClass());
             }
 
-            String specialNumberResult = transformFromSpecialNumber();
+            CharSequence specialNumberResult = transformFromSpecialNumber();
             if (null != specialNumberResult) {
                 return specialNumberResult;
             }
@@ -1904,7 +1909,7 @@
             if (getDecimalFormatSymbols().getMinusSign() == result.charAt(0)) {
                 if (formatToken.isFlagSet(FormatToken.FLAG_PARENTHESIS)) {
                     result = wrapParentheses(result);
-                    return result.toString();
+                    return result;
                 }
             } else {
                 if (formatToken.isFlagSet(FormatToken.FLAG_SPACE)) {
@@ -1933,7 +1938,7 @@
         /*
          * Transforms a Date to a formatted string.
          */
-        private String transformFromDateTime() {
+        private CharSequence transformFromDateTime() {
             int startIndex = 0;
             char currentConversionType = formatToken.getConversionType();
 
diff --git a/libcore/luni/src/test/java/tests/api/java/util/FormatterTest.java b/libcore/luni/src/test/java/tests/api/java/util/FormatterTest.java
index b2030c9..6f86818 100644
--- a/libcore/luni/src/test/java/tests/api/java/util/FormatterTest.java
+++ b/libcore/luni/src/test/java/tests/api/java/util/FormatterTest.java
@@ -800,6 +800,36 @@
         }
     }
 
+    @TestTargetNew(
+        level = TestLevel.COMPLETE,
+        notes = "Tests that supplying a Formattable works. See http://code.google.com/p/android/issues/detail?id=1767.",
+        method = "format",
+        args = {}
+    )
+    public void test_Formattable() {
+        Formattable ones = new Formattable() {
+            public void formatTo(Formatter formatter, int flags, int width, int precision) throws IllegalFormatException {
+                try {
+                    formatter.out().append("111");
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+        Formattable twos = new Formattable() {
+            public void formatTo(Formatter formatter, int flags, int width, int precision) throws IllegalFormatException {
+                try {
+                    formatter.out().append("222");
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+
+        assertEquals("aaa 111?", new Formatter().format("aaa %s?", ones).toString());
+        assertEquals("aaa 111 bbb 222?", new Formatter().format("aaa %s bbb %s?", ones, twos).toString());
+    }
+
     /**
      * @tests java.util.Formatter#out()
      */