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