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