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