/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package java.io;

import org.apache.harmony.luni.util.Msg;

/**
 * A specialized {@link Writer} for class for writing content to an (internal)
 * char array. As bytes are written to this writer, the char array may be
 * expanded to hold more characters. When the writing is considered to be
 * finished, a copy of the char array can be requested from the class.
 * 
 * @see CharArrayReader
 * 
 * @since Android 1.0
 */
public class CharArrayWriter extends Writer {

    /**
     * The buffer for characters.
     * 
     * @since Android 1.0
     */
    protected char[] buf;

    /**
     * The ending index of the buffer.
     * 
     * @since Android 1.0
     */
    protected int count;

    /**
     * Constructs a new CharArrayWriter which has a buffer allocated with the
     * default size of 32 characters. This buffer is also used as the
     * {@code lock} to synchronize access to this writer.
     * 
     * @since Android 1.0
     */
    public CharArrayWriter() {
        super();
        buf = new char[32];
        lock = buf;
    }

    /**
     * Constructs a new CharArrayWriter which has a buffer allocated with the
     * size of {@code initialSize} characters. The buffer is also used as the
     * {@code lock} to synchronize access to this writer.
     * 
     * @param initialSize
     *            the initial size of this CharArrayWriters buffer.
     * @throws IllegalArgumentException
     *             if {@code initialSize < 0}.
     * @since Android 1.0
     */
    public CharArrayWriter(int initialSize) {
        super();
        if (initialSize < 0) {
            throw new IllegalArgumentException(Msg.getString("K005e")); //$NON-NLS-1$
        }
        buf = new char[initialSize];
        lock = buf;
    }

    /**
     * Closes this writer. The implementation in CharArrayWriter does nothing.
     * 
     * @since Android 1.0
     */
    @Override
    public void close() {
        /* empty */
    }

    private void expand(int i) {
        /* Can the buffer handle @i more chars, if not expand it */
        if (count + i <= buf.length) {
            return;
        }

        char[] newbuf = new char[buf.length + (2 * i)];
        System.arraycopy(buf, 0, newbuf, 0, count);
        buf = newbuf;
    }

    /**
     * Flushes this writer. The implementation in CharArrayWriter does nothing.
     * 
     * @since Android 1.0
     */
    @Override
    public void flush() {
        /* empty */
    }

    /**
     * Resets this writer. The current write position is reset to the beginning
     * of the buffer. All written characters are lost and the size of this
     * writer is set to 0.
     * 
     * @since Android 1.0
     */
    public void reset() {
        synchronized (lock) {
            count = 0;
        }
    }

    /**
     * Returns the size of this writer, that is the number of characters it
     * stores. This number changes if this writer is reset or when more
     * characters are written to it.
     * 
     * @return this CharArrayWriter's current size in characters.
     * @since Android 1.0
     */
    public int size() {
        synchronized (lock) {
            return count;
        }
    }

    /**
     * Returns the contents of the receiver as a char array. The array returned
     * is a copy and any modifications made to this writer after calling this
     * method are not reflected in the result.
     * 
     * @return this CharArrayWriter's contents as a new char array.
     * @since Android 1.0
     */
    public char[] toCharArray() {
        synchronized (lock) {
            char[] result = new char[count];
            System.arraycopy(buf, 0, result, 0, count);
            return result;
        }
    }

    /**
     * Returns the contents of this CharArrayWriter as a string. The string
     * returned is a copy and any modifications made to this writer after
     * calling this method are not reflected in the result.
     * 
     * @return this CharArrayWriters contents as a new string.
     * @since Android 1.0
     */
    @Override
    public String toString() {
        synchronized (lock) {
            return new String(buf, 0, count);
        }
    }

    /**
     * Writes {@code count} characters starting at {@code offset} in {@code c}
     * to this writer.
     * 
     * @param c
     *            the non-null array containing characters to write.
     * @param offset
     *            the index of the first character in {@code buf} to write.
     * @param len
     *            maximum number of characters to write.
     * @throws IndexOutOfBoundsException
     *             if {@code offset < 0} or {@code len < 0}, or if
     *             {@code offset + len} is bigger than the size of {@code c}.
     * @since Android 1.0
     */
    @Override
    public void write(char[] c, int offset, int len) {
        // avoid int overflow
        // BEGIN android-changed
        // Exception priorities (in case of multiple errors) differ from
        // RI, but are spec-compliant.
        // made implicit null check explicit,
        // removed redundant check,
        // added null check, used (offset | len) < 0 instead of
        // (offset < 0) || (len < 0) to safe one operation
        if (c == null) {
            throw new NullPointerException(Msg.getString("K0047")); //$NON-NLS-1$
        }
        if ((offset | len) < 0 || len > c.length - offset) {
            throw new IndexOutOfBoundsException(Msg.getString("K002f")); //$NON-NLS-1$
        }
        // END android-changed
        synchronized (lock) {
            expand(len);
            System.arraycopy(c, offset, this.buf, this.count, len);
            this.count += len;
        }
    }

    /**
     * Writes the specified character {@code oneChar} to this writer.
     * This implementation writes the two low order bytes of the integer
     * {@code oneChar} to the buffer.
     * 
     * @param oneChar
     *            the character to write.
     * @since Android 1.0
     */
    @Override
    public void write(int oneChar) {
        synchronized (lock) {
            expand(1);
            buf[count++] = (char) oneChar;
        }
    }

    /**
     * Writes {@code count} number of characters starting at {@code offset} from
     * the string {@code str} to this CharArrayWriter.
     * 
     * @param str
     *            the non-null string containing the characters to write.
     * @param offset
     *            the index of the first character in {@code str} to write.
     * @param len
     *            the number of characters to retrieve and write.
     * @throws NullPointerException
     *             if {@code str} is null.
     * @throws StringIndexOutOfBoundsException
     *             if {@code offset < 0} or {@code len < 0}, or if
     *             {@code offset + len} is bigger than the length of
     *             {@code str}.
     * @since Android 1.0
     */
    @Override
    public void write(String str, int offset, int len) {
        if (str == null) {
            throw new NullPointerException(Msg.getString("K0047")); //$NON-NLS-1$
        }
        // avoid int overflow
        // BEGIN android-changed
        // Exception priorities (in case of multiple errors) differ from
        // RI, but are spec-compliant.
        // removed redundant check, used (offset | len) < 0
        // instead of (offset < 0) || (len < 0) to safe one operation
        if ((offset | len) < 0 || len > str.length() - offset) {
            throw new StringIndexOutOfBoundsException(Msg.getString("K002f")); //$NON-NLS-1$
        }
        // END android-changed
        synchronized (lock) {
            expand(len);
            str.getChars(offset, offset + len, buf, this.count);
            this.count += len;
        }
    }

    /**
     * Writes the contents of this CharArrayWriter to another Writer. The output
     * is all the characters that have been written to the receiver since the
     * last reset or since it was created.
     * 
     * @param out
     *            the non-null Writer on which to write the contents.
     * @throws NullPointerException
     *             if {@code out} is null.
     * @throws IOException
     *             if an error occurs attempting to write out the contents.
     * @since Android 1.0
     */
    public void writeTo(Writer out) throws IOException {
        synchronized (lock) {
            out.write(buf, 0, count);
        }
    }

    /**
     * Appends a char {@code c} to the CharArrayWriter. The method works the
     * same way as {@code write(c)}.
     * 
     * @param c
     *            the character appended to the CharArrayWriter.
     * @return this CharArrayWriter.
     * @since Android 1.0
     */
    @Override
    public CharArrayWriter append(char c) {
        write(c);
        return this;
    }

    /**
     * Appends a CharSequence {@code csq} to the CharArrayWriter. The method
     * works the same way as {@code write(csq.toString())}. If {@code csq} is
     * null, then it will be substituted with the string "null".
     * 
     * @param csq
     *            the CharSequence appended to the CharArrayWriter, may be null.
     * @return this CharArrayWriter.
     * @since Android 1.0
     */
    @Override
    public CharArrayWriter append(CharSequence csq) {
        if (null == csq) {
            append(TOKEN_NULL, 0, TOKEN_NULL.length());
        } else {
            append(csq, 0, csq.length());
        }
        return this;
    }

    /**
     * Append a subsequence of a CharSequence {@code csq} to the
     * CharArrayWriter. The first and last characters of the subsequence are
     * specified by the parameters {@code start} and {@code end}. The
     * CharArrayWriter.append({@code csq}) works the same way as
     * {@code CharArrayWriter.write(csq.subSequence(start, end).toString)}. If
     * {@code csq} is null, then it will be substituted with the string "null".
     * 
     * @param csq
     *            the CharSequence appended to the CharArrayWriter, may be null.
     * @param start
     *            the index of the first character in the CharSequence appended
     *            to the CharArrayWriter.
     * @param end
     *            the index of the character after the last one in the
     *            CharSequence appended to the CharArrayWriter.
     * @return this CharArrayWriter.
     * @throws IndexOutOfBoundsException
     *             if {@code start < 0}, {@code end < 0}, {@code start > end},
     *             or if {@code end} is greater than the length of {@code csq}.
     * @since Android 1.0
     */
    @Override
    public CharArrayWriter append(CharSequence csq, int start, int end) {
        if (null == csq) {
            csq = TOKEN_NULL;
        }
        String output = csq.subSequence(start, end).toString();
        write(output, 0, output.length());
        return this;
    }
}
