/**
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2004 satoshi akabane(akabane@logical-paradox.org)
 *
 */
package org.logical_paradox.common.io;

import java.io.*;

/**
 * 拡張可能なバイトバッファ
 * 
 * @author satoshi akabane@logical-paradox.org
 * @version 1.0
 */
public class ExpandableBuffer implements ByteStream {
	public static final int DEFAULT_BLOCKSIZE = 4096;		// デフォルトのブロックサイズ
	protected byte[] _buffer;									// バッファの実体
	protected final int _blocksize;							// 1回ごとに拡張するバイト数
	protected int _eob;										// 現在使用されているバッファの最後の位置
	protected int _seekp;										// 現在のシークポインタ
	
	/**
	 * コンストラクタ．
	 * デフォルトのブロックサイズで初期化される
	 */
	public ExpandableBuffer() {
		this(DEFAULT_BLOCKSIZE);
	}
	/**
	 * コンストラクタ．
	 * 指定されたブロックサイズで初期化される
	 * 
	 * @param blocksize ブロックサイズ(バッファ拡張処理時に自動的に確保されるバイトサイズ)
	 */
	public ExpandableBuffer(int blocksize) {
		_blocksize = blocksize;
		_buffer = new byte[blocksize];
		_eob = 0;
	}
	/**
	 * 現在のバッファのサイズを返す
	 * 
	 * @return サイズ
	 */
	public int size() {
		return _buffer.length;
	}
	/**
	 * シークポインタを再設定する
	 * 
	 * @param seekp シークポインタ
	 * @throws IOException シークポインタがバッファの終端を越えた
	 */
	public void seek(int seekp) throws IOException {
		_seekp = seekp;
	}
	/**
	 * バッファから1バイト読み込んで返す<br>
	 * 読み込んだ場合，シークポインタが1つ進む．読み込むデータが存在しない場合は-1が返る
	 * 
	 * @return 読み込んだバイトデータ(-1:もうデータがない)
	 * @throws IOException 入力中に何らかのエラーが発生した
	 */
	public int read() throws IOException {
		synchronized(this) {
			if(_seekp >= _eob) {
				return -1;
			}
			int b = (int)_buffer[_seekp++] & 0xff;
			return b;
		}
	}
	/**
	 * バイトデータを現在のシークポインタの位置へ書き込む
	 * @param i バイトデータ
	 * @throws IOException 出力処理中に何らかのエラーが発生した場合
	 */
	public void write(int i) throws IOException {
		synchronized(this) {
			if(_seekp >= size()) {
				// シークポインタが現在のバッファの終端を越えている場合は
				// バッファの再拡張
				expand();
			}
			_buffer[_seekp++] = (byte)(i & 0xff);
			if(_seekp > _eob) {
				_eob = _seekp;
			}
		}
	}
	/**
	 * バイトデータを現在のシークポインタの位置へ書き込む
	 * @param b バイトデータ
	 * @throws IOException 出力処理中に何らかのエラーが発生した場合
	 */
	public void write(byte b) throws IOException {
		write((int)b);
	}
	/**
	 * バイトデータを現在のシークポインタの位置へ書き込む
	 * @param b バイトデータ
	 * @throws IOException 出力処理中に何らかのエラーが発生した場合
	 */
	public void write(byte[] b) throws IOException {
		for(int i = 0; i < b.length; i++) {
			write(b[i]);
		}
	}
	/**
	 * バイトデータを現在のシークポインタの位置へ書き込む
	 * 指定されたバイト数分書き込むか、あるいは全て出力した場合に終了する
	 * @param b バイトデータ
	 * @param length 書き込み長さ
	 * @throws IOException 出力処理中に何らかのエラーが発生した場合
	 */
	public void write(byte[] b, int length) throws IOException {
		for(int i = 0; i < b.length && i < length; i++) {
			write(b[i]);
		}
	}
	/**
	 * 現在バッファとして使用されている領域のサイズを返す
	 * 
	 * @return 使用されているバイト数
	 */
	public int using() {
		return _eob;
	}
	/**
	 * このバッファの現在の状態を反映させた入力ストリームを返す
	 * 
	 * @return 入力ストリーム
	 */
	public InputStream getInputStream() {
		return new ExpBufferInputStream(this);
	}
	/**
	 * このバッファの現在の状態を反映させた出力ストリームを返す
	 * 
	 * @return 出力ストリーム
	 */
	public OutputStream getOutputStream() {
		return new ExpBufferOutputStream(this);
	}

	/**
	 * バッファを拡張する
	 */
	protected void expand() {
		synchronized(this) {
			int buffersize = size();
			byte[] newBuffer = new byte[buffersize + _blocksize];
			System.arraycopy(_buffer, 0, newBuffer, 0, buffersize);
			_buffer = newBuffer;
		}
	}
	/**
	 * ExpandableByteBufferの入力ストリーム
	 * 
	 * @author satoshi akabane@logical-paradox.org
	 * @version 1.0
	 */
	protected class ExpBufferInputStream extends InputStream {
		protected ExpandableBuffer _buffer;
		protected ExpBufferInputStream(ExpandableBuffer buffer) {
			_buffer = buffer;
		}
		/**
		 * バッファから1バイト読み込んで返す
		 * @return 読み込んだバイトデータ(-1:バッファの終端)
		 * @exception IOException 入出力処理中に何らかのエラーが発生した場合
		 */
		public int read() throws IOException {
			return _buffer.read();
		}
	}
	/**
	 * ExpandableByteBufferの出力ストリーム
	 * 
	 * @author satoshi akabane@logical-paradox.org
	 * @version 1.0
	 */
	protected class ExpBufferOutputStream extends OutputStream {
		protected ExpandableBuffer _buffer;
		protected ExpBufferOutputStream(ExpandableBuffer buffer) {
			_buffer = buffer;
		}

		/**
		 * バイトデータを出力する
		 * @param b バイトデータ
		 * @exception IOException 入出力処理中に何らかのエラーが発生した場合
		 */
		public void write(int b) throws IOException {
			_buffer.write(b);
		}
	}
	/**
	 * このバッファの内容をバイト列に変換して返す
	 * もともとバイト列ではあるが．
	 * 
	 * @return バッファの内容(バイト列)
	 */
	public byte[] byteStream() {
		byte[] buffer = new byte[_eob];
		System.arraycopy(_buffer, 0, buffer, 0, _eob);
		return buffer;
	}
}
