クリップボードと圧縮

MIDIを作れるJavaアプレットを作っているのだ。
前回で文字列データをプログラムと分けたのだ。

データ復元

アプレットって、間違ってブラウザ閉じちゃったり、予期せぬエラーで消えちゃったり、ページ移動なんかがあって、なにかとデータ消失の危険に晒されています。
だから、データをどこかに保持しておいて、データが消えてしまったときに復元したいのですが、アプレットを使っている以上ファイルにはそう気軽には記録できないのです。
アプレットから利用できて、アプレットが消えてもデータが残る、そんな都合がいいのは、そう、クリップボードです。たぶん。

クリップボード

Javaでクリップボードを使うならjava.awt.datatransfer.Clipboardです。
さて・・・・クリップボードなら使えると先ほど言ってしまったのですが実際どうなのでしょうか。
ファイル保存のときのようにセキュリティエラーが起こらなければよいのですが。
ということでテストしてみます。

public void clipboardTest() {
	try {
		Clipboard cb = getToolkit().getSystemClipboard();
	}catch (Exception ex) {
		System.out.println(ex.getMessage());
	}
}

結果は、

access denied (java.awt.AWTPermission accessClipboard)

だそうです。がっくり。
アプレットからクリップボードが利用できるなら一定時間ごとにクリップボードにバックアップを、と考えていたのですがどうもファイルの場合と扱いは同じのようです。
一応ユーザーの操作(Windowsの場合Ctrl+Cなど)によるクリップボードへのコピーはできるようなのでバックアップはユーザーに自主的にとってもらうことにしましょう。

圧縮と展開

自動バックアップという案があっけなく消え去ってしまったのですが、手動バックアップや一時保存からの再開などのために独自形式でデータを保存できるようにすることは必要かもしれません。
独自形式そのものはプログラミングとはあまり関係ないので省略しますが、実はMIDIデータというものは経験的に結構同じようなデータが続く傾向があるようで、かなり冗長なデータなので、圧縮してやるとデータサイズが劇的に縮まります(もっとも、ミディビで想定しているような単純な曲だとかえって大きくなることがあるのですが)。
そんなわけで、データの圧縮と展開をやってみることにします。
圧縮展開といってもいろいろあるでしょうが今回はパッケージ java.util.zipDeflaterInflaterを使います。
そしてデータの入出力を伴う今回の場合はOutputStreamInputStreamとくっついたDeflaterOutputStreamInflaterInputStreamを使ったほうが都合がよさそうです。
いきなりMIDIデータで本番というのも危なっかしいので簡単なデータでちょっと試してみましょう。

byte data[] = { (byte)0x83,(byte)0x81,(byte)0x83,(byte)0x65,(byte)0x83,(byte)0x49,(byte)0x83,(byte)0x58,(byte)0x81,(byte)0x40,(byte)0x82,(byte)0xA0,(byte)0x82,(byte)0xB0,(byte)0x82,(byte)0xEB,(byte)0x81,(byte)0x40,(byte)0x83,(byte)0x45,(byte)0x83,(byte)0x89,(byte)0x83,(byte)0x89,(byte)0x83,(byte)0x89,(byte)0x83,(byte)0x89,(byte)0x81,(byte)0x60,(byte)0x81,(byte)0x40,(byte)0x83,(byte)0x81,(byte)0x83,(byte)0x65,(byte)0x83,(byte)0x49,(byte)0x83,(byte)0x58,(byte)0x81,(byte)0x40,(byte)0x82,(byte)0xA0,(byte)0x82,(byte)0xAA,(byte)0x82,(byte)0xC1,(byte)0x82,(byte)0xC4,(byte)0x81,(byte)0x40,(byte)0x83,(byte)0x59,(byte)0x83,(byte)0x68,(byte)0x83,(byte)0x68,(byte)0x83,(byte)0x68,(byte)0x83,(byte)0x68,(byte)0x81,(byte)0x60,(byte)0x81,(byte)0x40,(byte)0x83,(byte)0x8F,(byte)0x83,(byte)0x8C,(byte)0x83,(byte)0x8F,(byte)0x83,(byte)0x8C,(byte)0x81,(byte)0x40,(byte)0x82,(byte)0xC7,(byte)0x82,(byte)0xB1,(byte)0x82,(byte)0xA9,(byte)0x82,(byte)0xC5,(byte)0x81,(byte)0x40,(byte)0x8F,(byte)0xE3,(byte)0x8F,(byte)0xB8,(byte)0x8E,(byte)0x75,(byte)0x8C,(byte)0xFC,(byte)0x81,(byte)0x60,(byte)0x81,(byte)0x40,(byte)0x83,(byte)0x81,(byte)0x83,(byte)0x65,(byte)0x83,(byte)0x49,(byte)0x83,(byte)0x58,(byte)0x81,(byte)0x40,(byte)0x82,(byte)0xA0,(byte)0x82,(byte)0xB0,(byte)0x82,(byte)0xEB,(byte)0x81,(byte)0x40,(byte)0x91,(byte)0xC5,(byte)0x82,(byte)0xBF,(byte)0x82,(byte)0xA0,(byte)0x82,(byte)0xB0,(byte)0x82,(byte)0xEB,(byte)0x81,(byte)0x40,(byte)0x83,(byte)0x81,(byte)0x83,(byte)0x65,(byte)0x83,(byte)0x49,(byte)0x83,(byte)0x58, };
System.out.println("元のデータ:"+HexStringOutputStream.toString(data));
// 圧縮
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DeflaterOutputStream dout = new DeflaterOutputStream(bout);
dout.write(data);
dout.close();
System.out.println("圧縮データ:"+HexStringOutputStream.toString(bout.toByteArray()));
// 展開
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
InflaterInputStream iin = new InflaterInputStream(bin);
byte data2[] = new byte[data.length];
iin.read(data2);
System.out.println("展開データ:"+HexStringOutputStream.toString(data2));

簡単という割にはでっかいデータですがこうでもしないと圧縮でデータが膨れ上がってしまうので勘弁してください。
ついでに横長なのも。
HexStringOutputStream.toStringってのはbyte配列を受け取って16進数の文字列で返す自作関数です。
で出力結果は次のようになります。

元のデータ:8381836583498358814082A082B082EB814083458389838983898389816081408381836583498358814082A082AA82C182C481408359836883688368836881608140838F838C838F838C814082C782B182A982C581408FE38FB88E758CFC816081408381836583498358814082A082B082EB814091C582BF82A082B082EB81408381836583498358
圧縮データ:789C6B6E6C4E6DF66C8E6874685AD0B4A1E975A343B36B73270436260079C8F2AB9A0E361D018A453667402058457F730F0803551C6FDAD8B4B2E968A343FFE3FE1D7DA53D7F304C00DB30F168D37EB86D505900042644A1
展開データ:8381836583498358814082A082B082EB814083458389838983898389816081408381836583498358814082A082AA82C182C481408359836883688368836881608140838F838C838F838C814082C782B182A982C581408FE38FB88E758CFC816081408381836583498358814082A082B082EB814091C582BF82A082B082EB81408381836583498358

なるほど確かに圧縮されて展開されて元のデータに戻っています。
実際に使うときは、展開するときに受け取るデータのサイズがわからないので大きめのバッファにreadして余分なデータを削るなどの作業が必要だったり、文字列を扱うためにFilterOutputStreamFilterInputStreamを多段に組み合わせたりする必要があるでしょう。

おまけ

さっきちらっと出ていたHexStringOutputStreamの中身を掲載しておきます。
ところでOutputStreamのこういう使い方ってありなんでしょうかね。

import java.io.*;

public class HexStringOutputStream extends FilterOutputStream {
	public void write(int b) throws IOException {
		int a=(b>>4)&0xF;
		out.write((a<0xA)?('0'+a):('A'+a-0xA));
		a=b&0xF;
		out.write((a<0xA)?('0'+a):('A'+a-0xA));
	}
	public HexStringOutputStream(OutputStream out) {
		super(out);
	}
	static public String toString(byte b[]) {
		try {
			ByteArrayOutputStream bs = new ByteArrayOutputStream();
			HexStringOutputStream hs = new HexStringOutputStream(bs);
			hs.write(b);
			hs.close();
			return new String(bs.toByteArray(),"US-ASCII");
		}catch (Exception ex) {
			return null;
		}
	}
}

要点

今回は実際のソースコードはありません。
ミディビの開発のほうがこっちに追いついていませんので。

続く