/*
 * Copyright 2003-2006 Sun Microsystems, Inc.  All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.lang;

import java.util.Arrays;
import sun.misc.FloatingDecimal;

/**
 * ...
 *
 * NOTE: Others classes should be revised, for example
 * redefining the default Appendable implementation in
 * <code>java.util.Formatter</code>,
 * and of course changing the places in which
 * <code>java.lang.StringBuilder</code> is used just to create a String in an
 * "append only" way.
 *
 * @author Jesús Viñuales
 * @version 1.02, 22/09/08
 * @see     java.lang.String
 * @see     java.lang.Object#toString()
 * @see     java.lang.StringBuffer
 * @see     java.lang.StringBuilder
 * @see     java.nio.charset.Charset
 * @since   JDK1.7
 */
public final class StringAppender implements Appendable {

    // Empirical tests have shown 7 as the best option, both for small number of
    // appends and for typical expands.
    // But of course, other options can/must be investigated...
    //
    // Note: an IPv4 String is composed of 7 significant parts :-)
    private final static int DEFAULT_INITIAL_ELEMENTS_LENGTH = 7;

    private final static String[] ASCII_CHAR_STRS = new String[0x80];
    static {
//TODO: This iniciatization implementation should be changed for
//      constants in the variable declaration...
        for (int i=0; i<ASCII_CHAR_STRS.length; i++)
            ASCII_CHAR_STRS[i] = String.valueOf((char)i);
    }

    String[] elements;
    int elementsCount = 0;
    int charCount = 0;

    /**
     * Package protected because can lead into confussion
     * (the parameter means a different thing than in StringBuilder!),
     * and seems not to be necessary because the internal array growth
     * doen't impact as much as in StringBuilder
     * (String array vs Char array)
     */
    StringAppender(int initialElements) {
	this.elements = new String[initialElements];
    }

    public StringAppender() {
	this.elements = new String[DEFAULT_INITIAL_ELEMENTS_LENGTH];
    }

    public StringAppender(String str) {
        this(DEFAULT_INITIAL_ELEMENTS_LENGTH);
	append(str);
    }

    public StringAppender(CharSequence seq) {
        this(DEFAULT_INITIAL_ELEMENTS_LENGTH);
        append(seq);
    }

    public StringAppender(StringAppender sa) {
        this(DEFAULT_INITIAL_ELEMENTS_LENGTH+sa.elementsCount);
        append(sa);
    }

    public StringAppender(String... strs) {
        this(DEFAULT_INITIAL_ELEMENTS_LENGTH+strs.length);
        append(strs);
    }

    public StringAppender(char... strs) {
        this(DEFAULT_INITIAL_ELEMENTS_LENGTH+strs.length);
        append(strs);
    }

    public StringAppender append(String str) {
        if (elementsCount >= elements.length)
            expandListCapacity(elementsCount);
        if (str==null) {
            elements[elementsCount++] = null;
            charCount += 4; // "null".length()==4
        }
        else {
            elements[elementsCount++] = str;
            charCount += str.length();
        }
        return this;
    }

    public StringAppender append(Object obj) {
        if (obj==null)
            return append((String)null);
        return append(obj.toString());
    }

    // No null checks...
    StringAppender append(int numStrsToAppend, String... strs) {
        int neededCount = elementsCount+numStrsToAppend-1;
        if (neededCount >= elements.length)
            expandListCapacity(neededCount);
        for (int i=0; i<numStrsToAppend; i++) {
            append(strs[i]);
        }
        return this;
    }

    public StringAppender append(String... strs) {
        if (strs==null)
            return append((String)null);
        return append(strs.length, strs);
    }

    public StringAppender append(StringAppender sa) {
        if (sa==null)
            return append((String)null);
        return append(sa.elementsCount, sa.elements);
    }

    public StringAppender append(Object... objs) {
        int neededCount = elementsCount+objs.length-1;
        if (neededCount >= elements.length)
            expandListCapacity(neededCount);
        for (Object obj : objs) {
            append(obj);
        }
        return this;
    }

    public StringAppender append(StringBuilder sb) {
        return append((AbstractStringBuilder)sb);
    }

    public StringAppender append(StringBuffer sb) {
        return append((AbstractStringBuilder)sb);
    }

    StringAppender append(AbstractStringBuilder sb) {
        if (sb==null)
            return append((String)null);
        // Share constructor.
        // Not a security issue because the created String is only used as a holder
        // For copying it in the toString() method
        return append(new String(0, sb.length(), sb.getValue()));
    }

    public StringAppender append(CharSequence s) {
        if (s==null)
            return append((String)null);
        if (s instanceof String)
            return this.append((String)s);
        else if (s instanceof AbstractStringBuilder)
            return this.append((AbstractStringBuilder)s);
        else
            return this.append(s.toString());
    }

    public StringAppender append(CharSequence s, int start, int end) {
        if (s==null)
            return append((String)null);
	if ((start < 0) || (end < 0) || (start > end) || (end > s.length()))
	    throw new IndexOutOfBoundsException(
                "start " + start + ", end " + end + ", s.length() "
                + s.length());
	int len = end - start;
	if (len == 0)
            return this;
        char[] sValue = new char[len];
        if (s instanceof String) {
            ((String)s).getChars(start, end, sValue, 0);
        }
        else if (s instanceof AbstractStringBuilder) {
            ((AbstractStringBuilder)s).getChars(start, end, sValue, 0);
        }
        else {
            int count = 0;
            for (int i=start; i<end; i++) {
                sValue[count++] = s.charAt(i);
            }
        }
        return append(new String(0, len, sValue));
    }

    public StringAppender append(char... str) {
        return append(new String(0, str.length, str));
    }

    public StringAppender append(char[] str, int offset, int len) {
        return append(new String(offset, str.length, str));
    }

    public StringAppender append(boolean b) {
//        if (elementsCount >= elements.length)
//            expandListCapacity(elementsCount);
//        if (b) {
//            elements[elementsCount++] = "true";
//            charCount += 5;
//        }
//        else {
//            elements[elementsCount++] = "false";
//            charCount += 6;
//        }
        // Invokes append(String) to use a copiled method...
        append(b ? "true" : "false");
        return this;
    }

    public StringAppender append(char c) {
//        if (elementsCount >= elements.length)
//            expandListCapacity(elementsCount);
//        elements[elementsCount++] = (c < '\u0080') ? ASCII_CHAR_STRS[c]
//                                                   : new String(0, 1, new char[]{c});
//        charCount++;
        // Invokes append(String) to use a copiled method...
        append((c < '\u0080') ? ASCII_CHAR_STRS[c]
                              : new String(0, 1, new char[]{c}));
        return this;
    }

    public StringAppender append(int i) {
        // Optimization for typical values
        if ((i >= 0) && (i <= 9))
            return append(ASCII_CHAR_STRS[0x30+i]);
        if (i == -1)
            return append("-1");
        return append(Integer.toString(i));
    }

    public StringAppender append(long l) {
        // Optimization for typical values
        if ((l >= 0) && (l <= 9))
            return append(ASCII_CHAR_STRS[0x30+(int)l]);
        if (l == -1)
            return append("-1");
        return append(Long.toString(l));
    }

    public StringAppender append(float f) {
        return append(new FloatingDecimal(f).toJavaFormatString());
    }

    public StringAppender append(double d) {
        return append(new FloatingDecimal(d).toJavaFormatString());
    }

    public StringAppender appendCodePoint(int codePoint) {
	if (!Character.isValidCodePoint(codePoint)) {
	    throw new IllegalArgumentException();
	}
	int n = 1;
	if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT) {
	    n++;
	}
	if (n == 1) {
	    return append((char) codePoint);
	} else {
            char[] a = new char[2];
	    Character.toSurrogates(codePoint, a, 0);
	    return append(new String(0, 1, a));
	}
    }

    public String toString() {
//        if (charCount==0)
//            return "";
        char value[] = new char[charCount];
        String.copyValuesInto(elements, value, 0, elementsCount);
        return new String(0,charCount,value);
    }


    void getChars(char dst[], int dstBegin) {
        String.copyValuesInto(elements, dst, dstBegin, elementsCount);
    }

    /**
     * Returns the length (character count), excluding the delimeters.
     *
     * @return  the length of the sequence of characters currently
     *          represented by this object
     */
    public int length() {
	return this.charCount;
    }

    private void expandListCapacity(int neededElementCount) {
        int newCapacity = (elements.length + 1) << 1;
        if (newCapacity < 0) {
            newCapacity = Integer.MAX_VALUE;
        } else if (neededElementCount >= newCapacity) {
            newCapacity = neededElementCount+DEFAULT_INITIAL_ELEMENTS_LENGTH;
        }
        elements = Arrays.copyOf(elements, newCapacity);
    }
}
