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

package java.lang;

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

/**
 * TODO: document, bla, bla...
 * 
 * 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.00, 09/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, java.io.Serializable {

    // Empirical tests have shown 6 as the best option, both for small number of
    // appends and for typical expands.
    // But of course, can/must be investigated other options...
    private final static int DEFAULT_INITIAL_ELEMENTS_LENGTH = 6;
    
    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(DEFAULT_INITIAL_ELEMENTS_LENGTH);
    }

    public StringAppender(String str) {
        this(DEFAULT_INITIAL_ELEMENTS_LENGTH+1);
	append(str);
    }
    
    public StringAppender(CharSequence seq) {
        this(DEFAULT_INITIAL_ELEMENTS_LENGTH+1);
        append(seq);
    }

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

    public StringAppender append(String str) {
        //if (str==null)
        //    str = "null";
        int newElementsCount = elementsCount+1;
        if (elements.length<newElementsCount) {
            expandListCapacity(newElementsCount);
        }
        elements[elementsCount] = str;
        elementsCount = newElementsCount;
        charCount += (str!=null) ? str.length() : 4;  // "null".length() == 4
        return this;
    }

    public StringAppender append(Object obj) {
        return append(String.valueOf(obj));
    }
    
    // No null check
    StringAppender append(int numStrsToAppend, String... strs) {
        if (strs==null)
            return append((String)null);
        int newElementsCount = elementsCount+numStrsToAppend;
        if (elements.length<newElementsCount) {
            expandListCapacity(newElementsCount);
        }
        System.arraycopy(strs, 0, elements, elementsCount, numStrsToAppend);
        for (int i=0; i<numStrsToAppend; i++) {
            String str = strs[i];
            charCount += (str!=null) ? str.length() : 4;  // "null".length() == 4
        }
        elementsCount = newElementsCount;
        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(StringBuilder 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.count, sb.value));
    }

    public StringAppender append(StringBuffer 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.count, sb.value));
    }
    
    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 StringBuilder)
            return this.append((StringBuilder)s);
        else if (s instanceof StringBuffer)
            return this.append((StringBuffer)s);
        else
            return this.append(s, 0, s.length());
    }

    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) {
        return append(b ? "true" : "false");
    }

    public StringAppender append(char c) {
        // optimizations for typical chars to append
        switch(c) {
            case ' ':
                return append(" ");
            case '\r':
                return append("\r");
            case '\n':
                return append("\n");
            case '\t':
                return append("\t");
            case '.':
                return append(".");
            case '/':
                return append("/");
            case '\\':
                return append("\\");
            case '-':
                return append("-");
            default: {
                char[] data = {c};
                return append(new String(0, 1, data));
            }
        }
    }

    public StringAppender append(int i) {
        // Optimization for typical values
        if (i == 0)
            return append("0");
        if (i == -1)
            return append("-1");
        if (i == 1)
            return append("1");
        if (i == Integer.MIN_VALUE)
            return append("-2147483648");
        if (i == Integer.MAX_VALUE)
            return append("2147483647");
        int len = (i < 0) ? Integer.stringSize(-i) + 1
                          : Integer.stringSize(i);
        char[] val = new char[len];
        Integer.getChars(i, len, val);
        return append(new String(0, len, val));
    }

    public StringAppender append(long l) {
        // Optimization for typical values
        if (l == 0)
            return append("0");
        if (l == -1)
            return append("-1");
        if (l == 1)
            return append("1");
        if (l == Long.MIN_VALUE)
            return append("-9223372036854775808");
        if (l == Long.MAX_VALUE)
            return append("9223372036854775807");
        int len = (l < 0) ? Long.stringSize(-l) + 1
                          : Long.stringSize(l);
        char[] val = new char[len];
        Long.getChars(l, len, val);
        return append(new String(0, len, val));
    }

    public StringAppender append(float f) {
        // Optimization for typical values
        if (f == 0f)
            return append("0");
        if (f == -1f)
            return append("-1");
        if (f == 1f)
            return append("1");
        return append(new FloatingDecimal(f).toJavaFormatString());
    }

    public StringAppender append(double d) {
        // Optimization for typical values
        if (d == 0d)
            return append("0");
        if (d == -1d)
            return append("-1");
        if (d == 1d)
            return append("1");
        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() {
        char value[] = new char[charCount];
        String.copyValuesInto(elements,0,value,0,elementsCount);
        return new String(0,charCount,value);
    }
    
    /**
     * Returns the length (character count).
     *
     * @return  the length of the sequence of characters currently 
     *          represented by this object
     */
    public int length() {
	return this.charCount;
    }
        
    void expandListCapacity(int minimumCapacity) {
        int newCapacity = (elements.length + 1) * 2;
        if (newCapacity < 0) {
            newCapacity = Integer.MAX_VALUE;
        } else if (minimumCapacity > newCapacity) {
            newCapacity = minimumCapacity;
        }
        elements = Arrays.copyOf(elements, newCapacity);

    }    
}
