CountWriter

import java.io.ByteArrayOutputStream;
import java.io.FilterWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

/**
 * 出力した文字数・バイト数を把握するためのライタ。<br/>
 * {@link IndentWriter}との併用には注意が必要。<br/>
 * {@link IndentWriter}は内部でインデントを余計に出力しているため、このクラスでラップしてしまうと正確なカウントができない。<br/>
 * 併用する際は必ず{@link IndentWriter}を外側にすること。
 */
public class CountWriter extends FilterWriter {

    private ByteArrayOutputStream baos;
    private Writer osw;
    private long[] bytes;
    private long[] chars;
    private String encoding;

    /**
     * カウンタ値は{@link #countBytes()}、{@link #countChars()}で取得する。
     * @param out
     */
    public CountWriter(Writer out) {
        this(out, null, null);
    }

    /**
     * カウンタの参照を指定するコンストラクタ。<br/>
     * refBytes[0]、refChars[0]をカウンタとして使用する。<br/>
     * @param out
     * @param refBytes
     * @param refChars
     */
    public CountWriter(Writer out, long[] refBytes, long[] refChars) {
        super(out);
        try {
            init(refBytes, refChars, null);
        } catch (UnsupportedEncodingException e) {
            throw new Error(e);
        }
    }

    /**
     * 文字コードを指定するコンストラクタ。<br/>
     * カウンタ値は{@link #countBytes()}、{@link #countChars()}で取得する。
     * @param out
     * @param encoding
     * @throws UnsupportedEncodingException
     */
    public CountWriter(Writer out, String encoding) throws UnsupportedEncodingException {
        this(out, null, null, encoding);
    }

    /**
     * カウンタの参照と文字コードを指定するコンストラクタ。<br/>
     * refBytes[0]とrefChars[0]をカウンタとして使用する。
     * @param out
     * @param refBytes
     * @param refChars
     * @param encoding
     * @throws UnsupportedEncodingException
     */
    public CountWriter(Writer out, long[] refBytes, long[] refChars, String encoding) throws UnsupportedEncodingException {
        super(out);
        init(refBytes, refChars, encoding);
    }

    public void init(long[] refBytes, long[] refChars, String encoding) throws UnsupportedEncodingException {
        if (refBytes != null) {
            if (refBytes.length == 0) {
                throw new IllegalArgumentException("refBytes is empty");
            }
            bytes = refBytes;
        } else {
            bytes = new long[1];
        }
        if (refChars != null) {
            if (refChars.length == 0) {
                throw new IllegalArgumentException("refChars is empty");
            }
            chars = refChars;
        } else {
            chars = new long[1];
        }
        if (encoding == null) {
            encoding = new OutputStreamWriter(System.out).getEncoding();
        }
        this.encoding = encoding;
        baos = new ByteArrayOutputStream();
        osw = new OutputStreamWriter(new CountOutputStream(baos, bytes), encoding);
    }

    public long countBytes() {return bytes[0];}
    public long countChars() {return chars[0];}
    public String getEncoding() {return encoding;}

    @Override
    public void write(char[] cbuf, int off, int len) throws IOException {
        super.write(cbuf, off, len);
        // 同じ内容をCountOutputStreamをラップしたWriterでメモリに書き込み、出力されたバイト数をカウントさせる
        osw.write(cbuf, off, len);
        osw.flush();
        // メモリに書き込んだ内容はその都度クリアする
        baos.reset();
        chars[0] += len;
    }

    @Override
    public void write(int c) throws IOException {
        super.write(c);
        osw.write(c);
        osw.flush();
        baos.reset();
        chars[0]++;
    }

    @Override
    public void write(String str, int off, int len) throws IOException {
        super.write(str, off, len);
        osw.write(str, off, len);
        osw.flush();
        baos.reset();
        chars[0] += len;
    }

    /**
     * カウンタのリセット
     */
    public void reset() {
        bytes[0] = 0;
        chars[0] = 0;
    }

}