    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.fukurou.util;
    017
    018import java.io.UnsupportedEncodingException;
    019import java.util.List;
    020import java.util.ArrayList;
    021import java.util.Arrays;
    022
    023/**
    024 * FixLengthData.java は、固定長データを作成するための簡易クラスです。
    025 *
    026 * データの項目(String[])を、それぞれの中で最大桁数にあわせて、スペース埋めします。
    027 * 各項目間に、追加するスペース数は、setAddLength( int[] ) メソッドで、
    028 * 各項目のタイプ(半角文字、全角混在、数字)の指定は、setType( int[] ) メソッド行います。
    029 *
    030 * このクラスは同期処理は保障されていません。
    031 *
    032 * @og.rev 5.6.6.0 (2013/07/05) keys の整合性チェックを追加
    033 *
    034 * @version  4.0
    035 * @author       Kazuhiko Hasegawa
    036 * @since    JDK5.0,
    037 */
    038public final class FixLengthData {
    039
    040        /** 項目タイプの定義変数：X:半角文字   {@value}        */
    041        public static final int X = 0 ;
    042        /** 項目タイプの定義変数：S:数字(前空白)        {@value}        */
    043        public static final int S = 1 ;                                                                         // 5.6.6.0 (2013/07/05) 前空白詰めに変更
    044        /** 項目タイプの定義変数：K:半角全角混在 {@value}        */
    045        public static final int K = 2 ;
    046        /** 項目タイプの定義変数：X9:数字(前ゼロ)       {@value}        */
    047        public static final int S0 = 3 ;                                                                        // 5.6.6.0 (2013/07/05) 前ゼロ詰めを変更
    048
    049        /** 項目間空白配列の定義変数：T:タブ区切り        {@value}        */
    050        public static final int T  = -1 ;                                                                       // 5.6.6.0 (2013/07/05) タブ区切り
    051        public static final int T2 = -2 ;                                                                       // 5.6.6.0 (2013/07/05) タブ区切り
    052        public static final int T3 = -3 ;                                                                       // 5.6.6.0 (2013/07/05) タブ区切り
    053        public static final int T4 = -4 ;                                                                       // 5.6.6.0 (2013/07/05) タブ区切り
    054
    055        private static final String[] TAB = new String[] { "","\t","\t\t","\t\t\t","\t\t\t\t" };
    056
    057        private static final String CR = System.getProperty("line.separator");
    058
    059        /** 初期 ENCODE 名 {@value}        */
    060        public static final String ENCODE = "Windows-31J" ;
    061
    062        private final int[]     addLen  ;               // 各データ間に追加するスペースを設定する。
    063        private final int[]     type    ;               // 各データの固定長形式。 0:半角文字 1:数字(前空白) 2:半角全角混在 3:数字(前ゼロ)
    064        private final int       size    ;               // データの個数
    065//      private String  encode  = ENCODE;       // 5.6.6.0 (2013/07/05) 廃止
    066
    067        private int[]           maxLen  = null;         // 内部変数。各データの最大長を記憶する。
    068        private String[]        fill_X  = null;         // スペースの文字列
    069        private String[]        fill_S  = null;         // ゼロの文字列
    070        private String[]        addSpc  = null;         // 内部変数。addLen で指定された文字数分の空白を管理します。
    071        private final List<String[]> list = new ArrayList<String[]>();
    072
    073        /**
    074         * データの項目数を指定して、オブジェクトを構築します。
    075         *
    076         * @og.rev 5.6.6.0 (2013/07/05) addLen の代わりに、addSpc で管理します。
    077         *
    078         * @param       len     データの項目数
    079         */
    080        public FixLengthData( final int len ) {
    081                size    = len ;
    082                addLen  = new int[size];
    083                type    = new int[size];
    084                maxLen  = new int[size];
    085        }
    086
    087        /**
    088         * 項目間空白配列と各項目のタイプ配列を指定して、オブジェクトを構築します。
    089         * どちらも、int型配列なので、順番に注意してください。
    090         *
    091         * @og.rev 5.6.6.0 (2013/07/05) 新規追加
    092         *
    093         * @param   inAddLen    データの項目間空白配列
    094         * @param   inType              データの各項目のタイプ配列
    095         * @see         #setAddLength( int[] )
    096         * @see         #setType( int[] )
    097         * @throws  IllegalArgumentException 引数が null の場合
    098         */
    099        public FixLengthData( final int[] inAddLen,final int[] inType ) {
    100                if( inAddLen == null || inType == null ) {
    101                        String errMsg = "項目間空白配列 または、項目のタイプ配列に、null は、指定できません。";
    102                        throw new IllegalArgumentException( errMsg );
    103                }
    104
    105                size    = inAddLen.length ;
    106
    107                addLen  = new int[size];
    108                type    = new int[size];
    109                maxLen  = new int[size];
    110
    111                setAddLength( inAddLen );
    112                setType( inType );
    113        }
    114
    115        /**
    116         * データの全角混在時に文字列長を算出するのに使用する エンコード方式を指定します。
    117         * 固定長では、基本的には、全角２Byte 半角１Byte で換算すべきです。
    118         * 設定値が、null または、ゼロ文字列の場合は、NullPointerException が throw されます。
    119         * 初期値は、"Windows-31J" です。
    120         *
    121         * @og.rev 5.6.6.0 (2013/07/05) 廃止。基本的に外部からエンコード指定する必要はない。
    122         *
    123         * @param       encode  全角混在時の固定長文字数算出エンコード
    124         * @throws  IllegalArgumentException 引数が null または、ゼロ文字列の場合
    125         */
    126//      public void setEncode( final String encode ) {
    127//              if( encode == null || encode.length() == 0 ) {
    128//                      String errMsg = "エンコードに、null または、ゼロ文字列は、指定できません。";
    129//                      throw new IllegalArgumentException( errMsg );
    130//              }
    131//
    132//              this.encode = encode;
    133//      }
    134
    135        /**
    136         * データの項目に対応した、固定時の間に挿入する空白文字数を指定します。
    137         * 初期値は、0 です。
    138         *
    139         * @og.rev 5.6.6.0 (2013/07/05) addLen の代わりに、addSpc で管理します。
    140         *
    141         * @param   inAddLen    データの項目間空白配列
    142         * @throws  IllegalArgumentException 引数のデータ件数が、コンストラクタで指定した数と異なる場合
    143         */
    144        public void setAddLength( final int[] inAddLen ) {
    145                int len = (inAddLen == null) ? 0 : inAddLen.length ;
    146                if( len != size ) {
    147                        String errMsg = "引数のデータ件数が、コンストラクタで指定した数と異なります。"
    148                                                                + "SIZE=[" + size + "] , 引数長=[" + len + "]" ;
    149                        throw new IllegalArgumentException( errMsg );
    150                }
    151
    152                System.arraycopy( inAddLen,0,addLen,0,size );
    153        }
    154
    155        /**
    156         * データの各項目のタイプ(半角文字、数字)を指定します。
    157         * X:半角文字の場合は、データを前方に、余った分を後方にスペースを埋めます。
    158         * S:数字(前空白)の場合は、データを後方に、余った分を空白を前方に埋めます。
    159         * S0:数字(前ゼロ)の場合は、データを後方に、余った分をゼロを前方に埋めます。
    160         * K:半角全角混在の場合は、ENCODE(Windows-31J) で文字数を求めるとともに、X:半角文字と同様の処理を行います。
    161         * 初期値は、X:半角文字 です。
    162         *
    163         * @param   inType      データの各項目のタイプ配列
    164         * @see #X
    165         * @see #S
    166         * @see #S0
    167         * @see #K
    168         * @throws  IllegalArgumentException 引数のデータ件数が、コンストラクタで指定した数と異なる場合
    169         */
    170        public void setType( final int[] inType ) {
    171                int len = (inType == null) ? 0 : inType.length ;
    172                if( len != size ) {
    173                        String errMsg = "引数のデータ件数が、コンストラクタで指定した数と異なります。"
    174                                                                + "SIZE=[" + size + "] , 引数長=[" + len + "]" ;
    175                        throw new IllegalArgumentException( errMsg );
    176                }
    177
    178                System.arraycopy( inType,0,type,0,size );
    179        }
    180
    181        /**
    182         * データの各項目に対応した配列データを設定します。
    183         * 配列データを登録しながら、各項目の最大データ長をピックアップしていきます。
    184         *
    185         * @param       inData  データの各項目の配列データ
    186         * @throws  IllegalArgumentException 引数のデータ件数が、コンストラクタで指定した数と異なる場合
    187         */
    188        public void addListData( final String[] inData ) {
    189                int len = (inData == null) ? 0 : inData.length ;
    190                if( len != size ) {
    191                        String errMsg = "引数のデータ件数が、コンストラクタで指定した数と異なります。"
    192                                                                + "SIZE=[" + size + "] , 引数長=[" + len + "]" ;
    193                        throw new IllegalArgumentException( errMsg );
    194                }
    195
    196                // 最大データ長の取得のみ行っておきます。
    197                try {
    198                        for( int i=0; i<size; i++ ) {
    199                                if( inData[i] != null ) {
    200                                        if( type[i] == K ) {
    201                                                len = inData[i].getBytes( ENCODE ).length ;
    202                                        }
    203                                        else {
    204                                                len = inData[i].length();
    205                                        }
    206                                        if( maxLen[i] < len ) { maxLen[i] = len; }
    207                                }
    208                        }
    209                }
    210                catch( UnsupportedEncodingException ex ) {
    211                        String errMsg = "データの変換に失敗しました。[" + ENCODE + "]" ;
    212                        throw new RuntimeException( errMsg,ex );
    213                }
    214                list.add( inData );
    215        }
    216
    217        /**
    218         * 指定の行に対する固定文字数に設定された文字列を返します。
    219         * 引数の行番号は、addListData(String[])メソッドで登録された順番です。
    220         *
    221         * @og.rev 5.6.6.0 (2013/07/05) addLen の代わりに、addSpc で管理します。
    222         *
    223         * @param       line    行番号(addListData で登録した順)
    224         *
    225         * @return      固定文字数に設定された文字列
    226         */
    227        public String getFixData( final int line ) {
    228                if( fill_X == null ) { makeSpace(); }   // 初期処理
    229
    230                String[] data = list.get( line );
    231                StringBuilder rtn = new StringBuilder();
    232                for( int i=0; i<size; i++ ) {
    233                        String dt = ( data[i] == null ) ? "" : data[i] ;
    234                        switch( type[i] ) {
    235                                case X: // 文字を出力してから、スペースで埋める。
    236                                                rtn.append( dt );
    237                                                rtn.append( fill_X[i].substring( dt.length() ) );
    238                                                break;
    239                                case S: // 空白で埋めてから、文字を出力する。
    240                                                rtn.append( fill_X[i].substring( dt.length() ) );
    241                                                rtn.append( dt );
    242                                                break;
    243                                case S0: // ゼロで埋めてから、文字を出力する。
    244                                                rtn.append( fill_S[i].substring( dt.length() ) );
    245                                                rtn.append( dt );
    246                                                break;
    247                                case K: // 全角を含む文字を出力してから、スペースで埋める。
    248                                                try {
    249                                                        int len = dt.getBytes( ENCODE ).length ;
    250                                                        rtn.append( dt );
    251                                                        rtn.append( fill_X[i].substring( len ) );
    252                                                }
    253                                                catch( UnsupportedEncodingException ex ) {
    254                                                        String errMsg = "データの変換に失敗しました。[" + ENCODE + "]" ;
    255                                                        throw new RuntimeException( errMsg,ex );
    256                                                }
    257                                                break;
    258                                default: // 基本的にありえない
    259                                                String errMsg = "不正な種別が指定されました[" + type[i] + "]" ;
    260                                                throw new RuntimeException( errMsg );
    261                                //              break;
    262                        }
    263                        rtn.append( addSpc[i] );                // 5.6.6.0 (2013/07/05) 項目間のスペースを出力
    264                }
    265                return rtn.toString();
    266        }
    267
    268        /**
    269         * データの各項目に対応した配列データを、すべて設定します。
    270         * ここでは、配列の配列型データを受け取り、内部的に、addListData( String[] )を
    271         * 実行しています。
    272         * 簡易的なメソッドです。
    273         *
    274         * @og.rev 5.6.6.0 (2013/07/05) 新規追加
    275         *
    276         * @param       inData  データの各項目の配列データの配列
    277         * @see         #addListData( String[] )
    278         * @throws  IllegalArgumentException 引数のデータ件数が、コンストラクタで指定した数と異なる場合
    279         */
    280        public void addAllListData( final String[][] inData ) {
    281                for( int i=0; i<inData.length; i++ ) {
    282                        addListData( inData[i] );
    283                }
    284        }
    285
    286        /**
    287         * 内部登録済みのすべてのデータを連結して出力します。
    288         * 連結時には、改行コードを設定しています。
    289         *
    290         * @og.rev 5.6.6.0 (2013/07/05) getAllFixData( StringBuilder ) を使用するように内部処理を変更
    291         *
    292         * @return      固定文字数に設定された文字列
    293         * @see         #getFixData( int )
    294         * @see         #getAllFixData( StringBuilder )
    295         */
    296        public String getAllFixData() {
    297                return getAllFixData( new StringBuilder( 1000 ) ).toString();
    298
    299//              StringBuilder buf = new StringBuilder( 1000 );
    300//
    301//              int len = list.size();
    302//              for( int i=0; i<len; i++ ) {
    303//                      buf.append( getFixData( i ) ).append( CR );
    304//              }
    305//
    306//              return buf.toString();
    307        }
    308
    309        /**
    310         * 内部登録済みのすべてのデータを引数のStringBuilderに連結して返します。
    311         * 連結時には、改行コードを設定しています。
    312         * return オブジェクトは、この引数と同一のオブジェクトです。
    313         *
    314         * @og.rev 5.6.6.0 (2013/07/05) 新規追加
    315         *
    316         * @param       buf 連結に使用する StringBuilder
    317         * @return      固定文字数に設定された StringBuilder(入力と同じ)
    318         * @see         #getFixData( int )
    319         * @see         #getAllFixData()
    320         */
    321        public StringBuilder getAllFixData( final StringBuilder buf ) {
    322                int len = list.size();
    323                for( int i=0; i<len; i++ ) {
    324                        buf.append( getFixData( i ) ).append( CR );
    325                }
    326
    327                return buf;
    328        }
    329
    330        /**
    331         * 固定文字列を作成するための種となるスペース文字列とゼロ文字列を作成します。
    332         *
    333         * @og.rev 5.6.6.0 (2013/07/05) addLen の代わりに、addSpc で管理します。
    334         */
    335        private void makeSpace() {
    336                fill_X  = new String[size];
    337                fill_S  = new String[size];
    338                addSpc  = new String[size];
    339                char[] ch ;
    340
    341                int startCnt = 0;                               // 先頭からの文字数
    342                for( int i=0; i<size; i++ ) {
    343//                      char[] ch = new char[maxLen[i]+addLen[i]];
    344                        // addLen に、T(タブ)が指定された場合、addSpc は、4の倍数になるように調整する。
    345                        startCnt += maxLen[i];
    346                        int addCnt = addLen[i] ;
    347                        if( addCnt < 0 ) {                                   // T,T2,T3,T4 のケース
    348        //                      addSpc[i] = TAB[-addCnt];
    349                                addCnt = (-4*addCnt) - (startCnt % 4);          // TAB数に合わせたスペースに換算した数
    350                        }
    351        //              else {
    352                                ch = new char[addCnt];
    353                                Arrays.fill( ch, ' ' );
    354                                addSpc[i] = String.valueOf( ch );
    355        //              }
    356                        startCnt += addCnt ;
    357
    358                        ch = new char[maxLen[i]];
    359                        switch( type[i] ) {
    360                                case S0:
    361                                                Arrays.fill( ch, '0' );
    362                                                fill_S[i] = String.valueOf( ch );
    363                                                break;
    364                                case X:
    365                                case S:
    366                                case K:
    367                                                Arrays.fill( ch, ' ' );
    368                                                fill_X[i] = String.valueOf( ch );
    369                                                break;
    370                                default: // 基本的にありえない
    371                                                String errMsg = "不正な種別が指定されました[" + type[i] + "]" ;
    372                                                throw new RuntimeException( errMsg );
    373                                //              break;
    374                        }
    375                }
    376        }
    377
    378        /**
    379         * 内部変数のデータと、最大値のキャッシュをクリアします。
    380         *
    381         * それ以外の変数(size、addLength、type)は、設定時のまま残っています。
    382         *
    383         */
    384        public void clear() {
    385                list.clear() ;
    386                maxLen  = new int[size];
    387                fill_X  = null;         // スペースの文字列
    388                fill_S  = null;         // ゼロの文字列
    389                addSpc  = null;         // 項目間空白
    390        }
    391}