    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 org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
    019import java.io.UnsupportedEncodingException;
    020import java.util.List;
    021import java.util.ArrayList;
    022import java.util.Arrays;
    023
    024import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
    025import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
    026
    027/**
    028 * FixLengthData.java は、固定長データを作成するための簡易クラスです。
    029 *
    030 * データの項目(String[])を、それぞれの中で最大桁数にあわせて、スペース埋めします。
    031 * 各項目間に、追加するスペース数は、setAddLength( int[] ) メソッドで、
    032 * 各項目のタイプ(半角文字、全角混在、数字)の指定は、setType( int[] ) メソッド行います。
    033 *
    034 * このクラスは同期処理は保障されていません。
    035 *
    036 * @og.rev 5.6.6.0 (2013/07/05) keys の整合性チェックを追加
    037 *
    038 * @version  4.0
    039 * @author       Kazuhiko Hasegawa
    040 * @since    JDK5.0,
    041 */
    042public final class FixLengthData {
    043
    044        /** 項目タイプの定義変数：X:半角文字   {@value}        */
    045        public static final int X = 0 ;
    046        /** 項目タイプの定義変数：S:数字(前空白)        {@value}        */
    047        public static final int S = 1 ;                                                                         // 5.6.6.0 (2013/07/05) 前空白詰めに変更
    048        /** 項目タイプの定義変数：K:半角全角混在 {@value}        */
    049        public static final int K = 2 ;
    050        /** 項目タイプの定義変数：X9:数字(前ゼロ)       {@value}        */
    051        public static final int S0 = 3 ;                                                                        // 5.6.6.0 (2013/07/05) 前ゼロ詰めを変更
    052
    053        /** 項目間空白配列の定義変数：T:タブ区切り        {@value}        */
    054        public static final int T  = -1 ;                                                                       // 5.6.6.0 (2013/07/05) タブ区切り
    055        public static final int T2 = -2 ;                                                                       // 5.6.6.0 (2013/07/05) タブ区切り
    056        public static final int T3 = -3 ;                                                                       // 5.6.6.0 (2013/07/05) タブ区切り
    057        public static final int T4 = -4 ;                                                                       // 5.6.6.0 (2013/07/05) タブ区切り
    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
    066        private int[]           maxLen  ;               // 内部変数。各データの最大長を記憶する。
    067        private String[]        fillX   ;               // スペースの文字列
    068        private String[]        fillS   ;               // ゼロの文字列
    069        private String[]        addSpc  ;               // 内部変数。addLen で指定された文字数分の空白を管理します。
    070        private final List<String[]> list = new ArrayList<>();
    071
    072        /**
    073         * データの項目数を指定して、オブジェクトを構築します。
    074         *
    075         * @og.rev 5.6.6.0 (2013/07/05) addLen の代わりに、addSpc で管理します。
    076         *
    077         * @param       len     データの項目数
    078         */
    079        public FixLengthData( final int len ) {
    080                size    = len ;
    081                addLen  = new int[size];
    082                type    = new int[size];
    083                maxLen  = new int[size];
    084        }
    085
    086        /**
    087         * 項目間空白配列と各項目のタイプ配列を指定して、オブジェクトを構築します。
    088         * どちらも、int型配列なので、順番に注意してください。
    089         *
    090         * @og.rev 5.6.6.0 (2013/07/05) 新規追加
    091         *
    092         * @param   inAddLen    データの項目間空白配列
    093         * @param   inType              データの各項目のタイプ配列
    094         * @see         #setAddLength( int[] )
    095         * @see         #setType( int[] )
    096         * @throws  IllegalArgumentException 引数が null の場合
    097         */
    098        public FixLengthData( final int[] inAddLen,final int[] inType ) {
    099                if( inAddLen == null || inType == null ) {
    100                        final String errMsg = "項目間空白配列 または、項目のタイプ配列に、null は、指定できません。";
    101                        throw new IllegalArgumentException( errMsg );
    102                }
    103
    104                size    = inAddLen.length ;
    105
    106                addLen  = new int[size];
    107                type    = new int[size];
    108                maxLen  = new int[size];
    109
    110                setAddLength( inAddLen );
    111                setType( inType );
    112        }
    113
    114        /**
    115         * データの項目に対応した、固定時の間に挿入する空白文字数を指定します。
    116         * 初期値は、0 です。
    117         *
    118         * @og.rev 5.6.6.0 (2013/07/05) addLen の代わりに、addSpc で管理します。
    119         *
    120         * @param   inAddLen    データの項目間空白配列(可変長引数)
    121         * @throws  IllegalArgumentException 引数のデータ件数が、コンストラクタで指定した数と異なる場合
    122         */
    123        public void setAddLength( final int... inAddLen ) {
    124                if( inAddLen != null && inAddLen.length != size ) {             // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
    125                        final String errMsg = "引数のデータ件数が、コンストラクタで指定した数と異なります。"
    126                                                                + "SIZE=[" + size + "] , 引数長=[" + inAddLen.length + "]" ;
    127                        throw new IllegalArgumentException( errMsg );
    128                }
    129
    130                System.arraycopy( inAddLen,0,addLen,0,size );
    131        }
    132
    133        /**
    134         * データの各項目のタイプ(半角文字、数字)を指定します。
    135         * X:半角文字の場合は、データを前方に、余った分を後方にスペースを埋めます。
    136         * S:数字(前空白)の場合は、データを後方に、余った分を空白を前方に埋めます。
    137         * S0:数字(前ゼロ)の場合は、データを後方に、余った分をゼロを前方に埋めます。
    138         * K:半角全角混在の場合は、ENCODE(Windows-31J) で文字数を求めるとともに、X:半角文字と同様の処理を行います。
    139         * 初期値は、X:半角文字 です。
    140         *
    141         * @param   inType      データの各項目のタイプ配列(可変長引数)
    142         * @see #X
    143         * @see #S
    144         * @see #S0
    145         * @see #K
    146         * @throws  IllegalArgumentException 引数のデータ件数が、コンストラクタで指定した数と異なる場合
    147         */
    148        public void setType( final int... inType ) {
    149                if( inType != null && inType.length != size ) {                 // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
    150                        final String errMsg = "引数のデータ件数が、コンストラクタで指定した数と異なります。"
    151                                                                + "SIZE=[" + size + "] , 引数長=[" + inType.length + "]" ;
    152                        throw new IllegalArgumentException( errMsg );
    153                }
    154
    155                System.arraycopy( inType,0,type,0,size );
    156        }
    157
    158        /**
    159         * データの各項目に対応した配列データを設定します。
    160         * 配列データを登録しながら、各項目の最大データ長をピックアップしていきます。
    161         *
    162         * @param       inData  データの各項目の配列(可変長引数)
    163         * @throws  IllegalArgumentException 引数のデータ件数が、コンストラクタで指定した数と異なる場合
    164         */
    165        public void addListData( final String... inData ) {
    166                if( inData != null && inData.length != size ) {                 // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
    167                        final String errMsg = "引数のデータ件数が、コンストラクタで指定した数と異なります。"
    168                                                                + "SIZE=[" + size + "] , 引数長=[" + inData.length + "]" ;
    169                        throw new IllegalArgumentException( errMsg );
    170                }
    171
    172                // 最大データ長の取得のみ行っておきます。
    173                try {
    174                        for( int i=0; i<size; i++ ) {
    175                                if( inData[i] != null ) {
    176                                        // 6.1.1.0 (2015/01/17) 意味の異なる変数の使いまわしをしているので、修正する。
    177                                        final int len = type[i] == K ? inData[i].getBytes( ENCODE ).length : inData[i].length();
    178                                        if( maxLen[i] < len ) { maxLen[i] = len; }
    179                                }
    180                        }
    181                }
    182                catch( final UnsupportedEncodingException ex ) {
    183                        final String errMsg = "データの変換に失敗しました。[" + ENCODE + "]" ;
    184                        throw new OgRuntimeException( errMsg,ex );
    185                }
    186                list.add( inData );
    187        }
    188
    189        /**
    190         * 指定の行に対する固定文字数に設定された文字列を返します。
    191         * 引数の行番号は、addListData(String[])メソッドで登録された順番です。
    192         *
    193         * @og.rev 5.6.6.0 (2013/07/05) addLen の代わりに、addSpc で管理します。
    194         *
    195         * @param       line    行番号(addListData で登録した順)
    196         *
    197         * @return      固定文字数に設定された文字列
    198         * @og.rtnNotNull
    199         */
    200        public String getFixData( final int line ) {
    201                if( fillX == null ) { makeSpace(); }    // 初期処理
    202
    203                final String[] data = list.get( line );
    204                final StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE );
    205                for( int i=0; i<size; i++ ) {
    206                        final String dt = ( data[i] == null ) ? "" : data[i] ;
    207                        switch( type[i] ) {
    208                                case X: // 文字を出力してから、スペースで埋める。
    209                                                rtn.append( dt );
    210                                                rtn.append( fillX[i].substring( dt.length() ) );
    211                                                break;
    212                                case S: // 空白で埋めてから、文字を出力する。
    213                                                rtn.append( fillX[i].substring( dt.length() ) );
    214                                                rtn.append( dt );
    215                                                break;
    216                                case S0: // ゼロで埋めてから、文字を出力する。
    217                                                rtn.append( fillS[i].substring( dt.length() ) );
    218                                                rtn.append( dt );
    219                                                break;
    220                                case K: // 全角を含む文字を出力してから、スペースで埋める。
    221                                                try {
    222                                                        final int len = dt.getBytes( ENCODE ).length ;
    223                                                        rtn.append( dt );
    224                                                        rtn.append( fillX[i].substring( len ) );
    225                                                }
    226                                                catch( final UnsupportedEncodingException ex ) {
    227                                                        final String errMsg = "データの変換に失敗しました。[" + ENCODE + "]" ;
    228                                                        throw new OgRuntimeException( errMsg,ex );
    229                                                }
    230                                                break;
    231                                default: // 基本的にありえない
    232                                                final String errMsg = "不正な種別が指定されました[" + type[i] + "]" ;
    233                                                throw new OgRuntimeException( errMsg );
    234                                //              break;
    235                        }
    236                        rtn.append( addSpc[i] );                // 5.6.6.0 (2013/07/05) 項目間のスペースを出力
    237                }
    238                return rtn.toString();
    239        }
    240
    241        /**
    242         * データの各項目に対応した配列データを、すべて設定します。
    243         * ここでは、配列の配列型データを受け取り、内部的に、addListData( String[] )を
    244         * 実行しています。
    245         * 簡易的なメソッドです。
    246         *
    247         * @og.rev 5.6.6.0 (2013/07/05) 新規追加
    248         *
    249         * @param       inData  データの各項目の配列データの配列
    250         * @see         #addListData( String[] )
    251         * @throws  IllegalArgumentException 引数のデータ件数が、コンストラクタで指定した数と異なる場合
    252         */
    253        public void addAllListData( final String[][] inData ) {
    254                for( int i=0; i<inData.length; i++ ) {
    255                        addListData( inData[i] );
    256                }
    257        }
    258
    259        /**
    260         * 内部登録済みのすべてのデータを連結して出力します。
    261         * 連結時には、改行コードを設定しています。
    262         *
    263         * @og.rev 5.6.6.0 (2013/07/05) getAllFixData( StringBuilder ) を使用するように内部処理を変更
    264         *
    265         * @return      固定文字数に設定された文字列
    266         * @og.rtnNotNull
    267         * @see         #getFixData( int )
    268         * @see         #getAllFixData( StringBuilder )
    269         */
    270        public String getAllFixData() {
    271                return getAllFixData( new StringBuilder( 1000 ) ).toString();
    272        }
    273
    274        /**
    275         * 内部登録済みのすべてのデータを引数のStringBuilderに連結して返します。
    276         * 連結時には、改行コードを設定しています。
    277         * return オブジェクトは、この引数と同一のオブジェクトです。
    278         *
    279         * @og.rev 5.6.6.0 (2013/07/05) 新規追加
    280         *
    281         * @param       buf 連結に使用する StringBuilder
    282         * @return      固定文字数に設定された StringBuilder(入力と同じ)
    283         * @og.rtnNotNull
    284         * @see         #getFixData( int )
    285         * @see         #getAllFixData()
    286         */
    287        public StringBuilder getAllFixData( final StringBuilder buf ) {
    288                final int len = list.size();
    289                for( int i=0; i<len; i++ ) {
    290                        buf.append( getFixData( i ) ).append( CR );
    291                }
    292
    293                return buf;
    294        }
    295
    296        /**
    297         * 固定文字列を作成するための種となるスペース文字列とゼロ文字列を作成します。
    298         *
    299         * @og.rev 5.6.6.0 (2013/07/05) addLen の代わりに、addSpc で管理します。
    300         */
    301        private void makeSpace() {
    302                fillX   = new String[size];
    303                fillS   = new String[size];
    304                addSpc  = new String[size];
    305                char[] ch ;
    306
    307                int startCnt = 0;                               // 先頭からの文字数
    308                for( int i=0; i<size; i++ ) {
    309                        // addLen に、T(タブ)が指定された場合、addSpc は、4の倍数になるように調整する。
    310                        startCnt += maxLen[i];
    311                        int addCnt = addLen[i] ;
    312                        if( addCnt < 0 ) {                                      // T,T2,T3,T4 のケース
    313        //                      addSpc[i] = TAB[-addCnt];
    314                                addCnt = -4 * addCnt - startCnt % 4;            // TAB数に合わせたスペースに換算した数          6.9.7.0 (2018/05/14) PMD
    315                        }
    316        //              else {
    317                                ch = new char[addCnt];
    318                                Arrays.fill( ch, ' ' );
    319                                addSpc[i] = String.valueOf( ch );
    320        //              }
    321                        startCnt += addCnt ;
    322
    323                        ch = new char[maxLen[i]];
    324                        switch( type[i] ) {
    325                                case S0:
    326                                                Arrays.fill( ch, '0' );
    327                                                fillS[i] = String.valueOf( ch );
    328                                                break;
    329                                case X:
    330                                case S:
    331                                case K:
    332                                                Arrays.fill( ch, ' ' );
    333                                                fillX[i] = String.valueOf( ch );
    334                                                break;
    335                                default: // 基本的にありえない
    336                                                final String errMsg = "不正な種別が指定されました[" + type[i] + "]" ;
    337                                                throw new OgRuntimeException( errMsg );
    338                                //              break;
    339                        }
    340                }
    341        }
    342
    343        /**
    344         * 内部変数のデータと、最大値のキャッシュをクリアします。
    345         *
    346         * それ以外の変数(size、addLength、type)は、設定時のまま残っています。
    347         *
    348         */
    349        public void clear() {
    350                list.clear() ;
    351                maxLen  = new int[size];
    352                fillX   = null;         // スペースの文字列
    353                fillS   = null;         // ゼロの文字列
    354                addSpc  = null;         // 項目間空白
    355        }
    356}