プログラミング雑技談

プログラミング雑技談

外部仕様の検討とファイルアクセス関連処理

宮坂電人
2008/4/16

前のページ1 2

ほかに検討すべき項目

 ほかに検討すべき項目として、

・ツールの設定を保存/ロードする(GUI版のみ)
・Shift JIS以外の文字コードを取り扱えるようにする

が思いつきますが、これらをどうするかはあと回しにします。というのも現時点では、まだツールプログラムをどう実装するかという詳細が決まっていないので、これら細かい仕様については検討する優先順位がまだ低いからです。

Model側の実装検討

 Javaが多重継承できる仕様なら、ToolModelというベースクラスを実装継承させるところですが、そうはせずにToolModelというインタフェイスを決め、それをimplementsする方向で考えます。現段階ではToolModelはList 1のようにしておきます。

List 1 初期段階のToolModel
public interface ToolModel {
    public boolean toolModelMain(ToolOutside iOutside);
}

 toolModelMainメソッドはツールの起動を要求するメソッドです。ツールが正常終了したならtrueを返し、そうでないならfalseを返すように実装します。引数として与えられているiOutsideはツールを呼び出した側が用意するオブジェクトです。ToolOutsideはツールオブジェクトから外界(主にView側を意味する)にアクセスするための切り口となる型です。これはList 2のようにします。各メソッドの中身が用意されていないのはまだ、どう実装すべきか決定していないからですが、おそらくはModel-View-ControllerでいうところのController側で実装することになるでしょう。

List 2 ツールオブジェクトから外界にアクセスするためのToolOutside
import java.util.*;

public class ToolOutside {

    //iBytesの内容を標準出力に送る。
    public void setOutput(byte[] iBytes) {
        setOutput(iBytes,0,iBytes.length);
    }

    //iBytesの内容のうちiOffsetから始まる位置よりiSizeバイトぶんを標準出力に送る。
    public void setOutput(byte[] iBytes,int iOffset,int iSize) {
        System.out.println("ToolOutside#setOutput");
    }

    //標準入力の内容を取り出す。
    public byte[] getInput() {
        System.out.println("ToolOutside#getInput");
        return new byte[0];
    }

    //iKeyで示すパラメータを取得する。
    public Object getParam(Object iKey) {
        System.out.println("ToolOutside#getParam");
        return null;
    }

    //パラメータの一覧を取得する。
    public Set getParamSet() {
        System.out.println("ToolOutside#getParamSet");
        return new HashSet();
    }

    //ファイル名の一覧を取得する。
    public String[] getFilenames() {
        System.out.println("ToolOutside#getFilenames");
        return new String[0];

    }
}

 ToolModelとToolOutsideがきちんと実装できたかどうかの確認用に、List 3のようなTestクラスを作ります。

List 3 ToolModelとToolOutsideをテストするクラス
public class Test implements ToolModel {

    public boolean toolModelMain(ToolOutside iOutside){

        ……(ここにツール処理を記述する)……

        return true;
    }

    public static void main(String args[]){
        System.out.println("* test start *");
        Test aObj = new Test();
        ToolOutside aOutside = new ToolOutside();
        aObj.toolModelMain(aOutside);
        System.out.println("* test end *");
    }
}

支援サブルーチンの実装

 ツールの詳細を実装していきたいところですが、先に支援サブルーチンの実装をボトムアップ的に進めます。チーム開発の場合は別部隊に任せるところですが、なにぶん1人でやっているので仕方ありません。

 ファイルアクセス関連の処理はFilesMiscというクラスにまとめます。ここにfileToBytesとbytesToFileを記述します。これはList 4のようになります。例外クラスをどうするかで悩みましたが、ツール側で細かく例外をフォローするメリットが見い出せないので、List 5のようなToolExceptionという例外の型を用意し、ツール内で発生しうる例外はすべてこの型のオブジェクトにしました。ユーザに対するエラーメッセージはコンストラクタで指定した文字列にしてしまいます。

List 4 ファイル関連のアクセスを行うクラス
import java.io.*;

public class FilesMisc {

    //iFilenameで指定のファイルの内容をbyte配列にして返す。
    static byte[] fileToBytes(String iFilename) throws ToolException {
        byte[] aBuffer = new byte[0];
        try{
            File aFile = new File(iFilename);
            int aFileLength = (int)aFile.length(); //ファイルサイズ
            if(aFileLength < 1){
                throw new ToolException();
            }
            aBuffer = new byte[aFileLength];
            FileInputStream aFIS = new FileInputStream(aFile);
            aFIS.read(aBuffer,0,aFileLength);
            aFIS.close();
        }
        catch(Exception e){
            throw new ToolException("can not read " + iFilename);
        }
        return aBuffer;
    }

    //iBytesの内容のうちiOffsetから始まる位置よりiSizeバイトぶんを
    //iFilenameで指定のファイルにする。
    static void bytesToFile(byte[] iBytes,int iOffset,int iSize,
                            String iFilename) throws ToolException {
            try{
                File aFile = new File(iFilename);
                //ファイルを削除する。
                if(aFile.exists()){
                    aFile.delete();
                }
                //ファイルを作成する。
                if(!aFile.createNewFile()){
                    throw new ToolException();
                }
                //ファイルにiBytesの内容を書き込む。
                FileOutputStream aFOS = new FileOutputStream(aFile);
                aFOS.write(iBytes,iOffset,iSize);
                aFOS.close();
            }
            catch(Exception e){
                throw new ToolException("can not create " + iFilename);
            }
        }
}


List 5 例外クラスToolException

public class ToolException extends Exception {
    public ToolException(String iErr){
        super(iErr);
    }
    public ToolException(){
        super("(Unknown exception)");
    }
}

FilesMiscの動作テスト

 作ったプログラムは実際に使用する前にテストをする必要があります。ボトムアップ的に作成した場合、できあがったプログラムをテストして積み重ねるアプローチがとりやすいのは大きなメリットでしょう。先ほどList 3に示したTest.javaを少しいじることで簡単にテストできます。List 6のようになります。List 6ではtest.jarをtest.1とtest.2に二分割し、そのあとで合成したファイル(test.3)を作っています。test.jarとtest.3を比較すれば、きちんと分割して合成できたかどうかが確認できます。

List 6 FilesMiscの動作テスト

public class Test implements ToolModel {

    private void testFilesMisc1() throws ToolException {
        //ファイルの二分割テスト(test.jar -> test.1,test.2)
        byte[] aBuff = FilesMisc.fileToBytes("test.jar");
        int aBuffSize = aBuff.length;
        System.out.println("aBuff.length = " + aBuffSize);
        if(aBuffSize < 2){
            throw new ToolException();
        }
        int aHalf = aBuffSize / 2;
        FilesMisc.bytesToFile(aBuff,0,aHalf,"test.1");
        FilesMisc.bytesToFile(aBuff,aHalf,aBuffSize - aHalf,"test.2");
    }

    private void testFilesMisc2() throws ToolException {
        //ファイルの合成テスト(test.1,test.2 -> test.3)
        byte[] aBuff1 = FilesMisc.fileToBytes("test.1");
        byte[] aBuff2 = FilesMisc.fileToBytes("test.2");
        byte[] aBuff3 = new byte[aBuff1.length + aBuff2.length];
        int aI;
        for(aI = 0; aI < aBuff1.length; aI++){
            aBuff3[aI] = aBuff1[aI];
        }
        for(aI = 0; aI < aBuff2.length; aI++){
            aBuff3[aBuff1.length + aI] = aBuff2[aI];
        }
        FilesMisc.bytesToFile(aBuff3,0,aBuff3.length,"test.3");
    }

    public boolean toolModelMain(ToolOutside iOutside){
        try{
            testFilesMisc1();
            testFilesMisc2();
        }
        catch(ToolException iE){
            iE.printStackTrace();
        }
        return true;
    }
    ……(略)……
}

BytesMiscの実装

 残っている支援サブルーチンの実装は、

・OneLinePicker
・BytesBuilder
・BytesMisc

ですが、難易度とほかのモジュールへの機能提供を考えて、まずBytesMiscを片付けます。

 BytesMiscで提供される機能はC言語の標準ライブラリに用意されているものが多いので、同じような機能がJavaの標準ライブラリにもあることを期待したのですが、オブジェクト(Object)ベースで利用されることを意識しているせいか、byteやbyte配列の汎用的なルーチンは見当たりません。Table 5で考えたうちmemで始まるメソッドは単純なので、List 7のように簡単に実装できます。isで始まるメソッドも単純ですが、C言語のctype.hの実装を知っている人ならテーブル形式で判断を得る方式にするでしょう。また、そうする理由が実行効率にあることもご存じでしょう。次回は、このあたりの説明から始めます。

List 7 BytesMiscのうちで名前がmemで始まるメソッド

public class BytesMisc {

    //iBAの内容のうちiBeginから始まる位置よりiSizeバイトぶんで最初に見つかる
    //iPatの位置を返す。見つからない場合は-1を返す。
    static int memchr(byte[] iBA,int iBegin,int iSize,byte iPat) {
        while(iSize > 0){
            if(iBA[iBegin] == iPat){
                return iBegin;
            }
            ++iBegin;
            --iSize;
        }
        return -1;
    }

    //iBAのiBeginから始まる位置からとiBBのiBegBから始まる位置よりiSizeぶんを
    //比較する。すべて一致すれば0を返し、一致しない場合はiBA側がiBB側より小さい
    //ならマイナス値を返し、そうでないならプラス値を返す。
    static int memcmp(byte[] iBA,int iBegA,byte[] iBB,int iBegB,int iSize) {
        while(iSize > 0){
            int aRet = (int)iBA[iBegA] - (int)iBB[iBegB];
            if(aRet != 0){
                return aRet;
            }
            ++iBegA;
            ++iBegB;
            --iSize;
        }
        return 0;
    }

    //iSrcのiBegSから始まる位置よりiSizeぶんをiDestのiBegDからの位置へコピーする。
    static void memmove(byte[] iDest,int iBegD,byte[] iSrc,int iBegS,int iSize) {
    if((iDest != iSrc) || (iBegD <= iBegS)){
            while(iSize > 0){
                iDest[iBegD++] = iSrc[iBegS++];
                --iSize;
            }
        }else{
            iBegS += iSize;
            iBegD += iSize;
            while(iSize > 0){
                iDest[--iBegD] = iSrc[--iBegS];
                --iSize;
            }
        }
    }

    //iDestのiBegDからの位置に対してiPatをiSizeぶんだけ埋める。
    static void memset(byte[] iDest,int iBegD,byte iPat,int iSize) {
        while(iSize > 0){
            iDest[iBegD++] = iPat;
            --iSize;
        }
    }
}


本連載は、ソフトバンククリエイティブ刊行の『C MAGAZINE』に掲載された記事を、同社の許可を得て転載するものです。

なお、 Webでの連載として転載するに当たり、若干表現を変更している点があります(例えば「本書は」としている部分は「本連載は」としていることや図版などの省略など)。その点ご了承ください。

前のページ1 2


 

自分戦略研究所、フォーラム化のお知らせ

@IT自分戦略研究所は2014年2月、@ITのフォーラムになりました。

現在ご覧いただいている記事は、既掲載記事をアーカイブ化したものです。新着記事は、 新しくなったトップページよりご覧ください。

これからも、@IT自分戦略研究所をよろしくお願いいたします。