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.util.Iterator;
019import java.util.Map;
020import java.util.HashMap;
021import java.util.NoSuchElementException;                                                // 8.5.5.1 (2024/02/29) spotbugs IT_NO_SUCH_ELEMENT
022
023import org.opengion.fukurou.system.HybsConst;
024
025/**
026 * JSONScan.java は、JSONで行うためのUtilクラスです。
027 *
028 * @og.rev 8.0.2.0 (2021/11/30) 新規作成
029 * @og.group ユーティリティ
030 *
031 * @version  8.0
032 * @author   LEE.
033 * @since    JDK17.0,
034 */
035public final class JSONScan implements Iterator<String> {
036        /** システムの改行コード */
037        private static final String CR                  = HybsConst.CR;
038        /** StringBilderなどの初期値 */
039        private static final int BUFFER_MIDDLE  = HybsConst.BUFFER_MIDDLE;
040
041        /** 8.1.1.0 (2022/02/04) 大括弧([])と制御文字(改行等)も削除対象にします。 */
042        // 8.5.4.2 (2024/01/12) PMD 7.0.0 FieldDeclarationsShouldBeAtStartOfClass
043        private static final String JSON_TRIM = "\"{}[] ";
044
045        /** 処理対象の文字列 */
046        private final String            orgStr;
047        /** 開始文字 */
048        private final char                      stCh;
049        /** 終了文字 */
050        private final char                      edCh;
051        /** 検索範囲の開始位置 */
052        private final int                       stScan;
053        /** 検索範囲の終了位置 */
054        private final int                       edScan;
055        /** 処理途中の切り出し開始位置 */
056        private int                                     stAdrs;
057        /** 処理途中の切り出し終了位置 */
058        private int                                     edAdrs;
059
060        /**
061         * コンストラクタ
062         * 処理対象の文字列を解析する JSONScan のインスタンスを作成します。
063         * 検索範囲の開始位置は、ゼロ(0) で初期化されます。
064         * 検索範囲の終了位置は、文字列の長さ で初期化されます。
065         *
066         * @param       orgStr  処理対象の文字列
067         * @param       stCh    開始文字
068         * @param       edCh    終了文字
069         */
070        public JSONScan( final String orgStr, final char stCh, final char edCh ) {
071                this( orgStr, stCh, edCh, 0, orgStr.length() );
072        }
073
074        /**
075         * コンストラクタ
076         * 処理対象の文字列を解析する JSONScan のインスタンスを作成します。
077         *
078         * @param       orgStr  処理対象の文字列
079         * @param       stCh    開始文字
080         * @param       edCh    終了文字
081         * @param       stScan  検索範囲の開始位置
082         * @param       edScan  検索範囲の終了位置
083         */
084        public JSONScan( final String orgStr, final char stCh, final char edCh , final int stScan , final int edScan ) {
085                this.orgStr                             = orgStr;                                                                               // 処理対象の文字列
086                this.stCh                               = stCh;                                                                                 // 開始文字
087                this.edCh                               = edCh;                                                                                 // 終了文字
088                this.stScan                             = stScan;                                                                               // 検索範囲の開始位置
089                this.edScan                             = edScan;                                                                               // 検索範囲の終了位置
090                stAdrs                                  = 0;                                                                                    // 処理途中の切り出し開始位置
091                edAdrs                                  = stScan;                                                                               // 処理途中の切り出し終了位置(検索範囲の最初のアドレス)
092        }
093
094        /**
095         * 検索範囲から開始文字がまだあるかどうかを判定します。
096         * このメソッドが true を返す場合、それ以降の引数のない next() への
097         * 呼び出しは適切に文字列を返します。
098         *
099         * @return      文字列内の現在の位置の後ろに 1 つ以上の開始文字がある場合だけ true、そうでない場合は false
100         */
101        @Override       // Iterator
102        public boolean hasNext() {
103                // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要
104                // 一気に判定します。
105                stAdrs = orgStr.indexOf( stCh, edAdrs );
106                edAdrs = orgStr.indexOf( edCh, stAdrs+1 );
107
108                return stAdrs >= 0 && stAdrs <= edScan && edAdrs >= 0 && edAdrs <= edScan ;
109
110//              stAdrs = orgStr.indexOf( stCh, edAdrs );
111//              // 見つからないか、検索範囲を超えた場合
112//              if( stAdrs < 0 || stAdrs > edScan ) {
113//                      return false;
114//              } else {
115//                      edAdrs = orgStr.indexOf( edCh , stAdrs+1 );
116//                      // 見つからないか、検索範囲を超えた場合
117//                      if( edAdrs < 0 || edAdrs > edScan ) {
118//                              return false;
119//                      }
120//              }
121//              return true;
122        }
123
124        /**
125         * 検索範囲から次の文字列を返します。
126         *
127         * @return      処理途中の切り出し文字列
128         * @throws      NoSuchElementException  Iteratorの継承
129         *
130         * @og.rev 8.5.5.1 (2024/02/29) spotbugs IT_NO_SUCH_ELEMENT (NoSuchElementException の追加)
131        */
132        @Override       // Iterator
133        public String next() {
134                if( orgStr == null ) {
135                        final String errMsg = "切り出し文字列が指定されていません。";
136                        throw new NoSuchElementException( errMsg );
137                }
138                return orgStr.substring( stAdrs+1 , edAdrs );
139        }
140
141        /**
142         * 検索範囲から開始文字の繰り返し数を数えます。
143         *
144         * @return      開始文字の出現回数
145         */
146        public int countBlock() {
147                final String subStr = orgStr.substring( stScan , edScan );
148                final long cnt = subStr.chars().filter(ch -> ch == stCh).count();
149                return Math.toIntExact(cnt);
150        }
151
152        /**
153         * JSON形式 から Mapオブジェクト に変更します。
154         *
155         * 「カンマ(,)」と「コロン(:)」キーワードで分割し、キーとラベルのペア情報を
156         * マッピングします。
157         *
158         * @param       csvData JSON形式の文字列 (例:{"key1":"value1","key2":"value2"})
159         * @return      Mapオブジェクト
160         */
161        public static Map<String,String> json2Map( final String csvData ) {
162                final Map<String,String> map = new HashMap<>();
163                // 「カンマ(,)」で分割
164                final String[] ary = StringUtil.csv2Array( csvData );
165                for( final String str : ary ) {
166                        // 「コロン(:)」で分割
167                        final String[] kv = str.split(":", 2);
168                        if( kv.length == 2 ) {
169                                map.put( json2Trim(kv[0]), json2Trim(kv[1]) );
170                        }
171                }
172                return map;
173        }
174
175        /**
176         * Mapオブジェクト から JSON形式 に変更します。
177         *
178         * @param       prmMap  Mapオブジェクト
179         * @return      JSON形式の文字列 (例:{"key1":"value1","key2":"value2"})
180         */
181        public static String map2Json( final Map<String,String> prmMap ) {
182                final StringBuilder buf = new StringBuilder(BUFFER_MIDDLE);
183                if( prmMap != null ) {
184                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ShortVariable i ⇒ ii に変更
185                        int ii = 0;
186                        buf.append( '{' )
187                                .append( CR );
188
189                        for( final Map.Entry<String, String> entry : prmMap.entrySet() ) {
190                                if( ii == 0 ) { buf.append( '"' ); }
191                                else { buf.append( ",\"" ); }
192                                ii++;
193
194                                buf.append( entry.getKey() )
195                                        .append( "\":\"" )
196                                        .append( entry.getValue() )
197                                        .append( '"' )
198                                        .append( CR );
199                        }
200                        buf.append( '}' );
201                }
202                return buf.toString();
203        }
204
205        /**
206         * 「カンマ(,)」区切り文字で連結された String を、配列に分解して、その値を返します。
207         * ヌル値(null)は 空白 に変換した値を返します。
208         *
209         * @param       csvData 元のデータ
210         * @return      文字列配列
211         */
212        public static String[] json2Array( final String csvData ) {
213                final String[] ary = StringUtil.csv2Array( csvData );
214                for( int i=0; i<ary.length; i++ ) {
215                        ary[i] = "null".equals(ary[i]) ? "" : json2Trim( ary[i] ) ;
216                }
217                return ary;
218        }
219
220        /**
221//       * 前後の二重引用符(")、中括弧({})、スペースを削除します。
222         * 前後の二重引用符(")、中括弧({})、大括弧([])、スペース、制御文字を削除します。
223         *
224         * @og.rev 8.1.1.0 (2022/02/04) 大括弧([])と制御文字(改行等)も削除対象にします。
225         *
226         * @param       str     文字列
227//       * @return      前後の二重引用符(")、中括弧({})、スペースを削除した文字列
228         * @return      前後の二重引用符(")、中括弧({})、大括弧([])、スペース、制御文字を削除した文字列
229         */
230        private static String json2Trim( final String str ) {
231                int st = 0;
232                int ed = str.length();
233                while( st < ed ) {
234                        final char ch = str.charAt(st);
235//                      if( ch == '"' || ch == '{'  || ch == '}' || ch == ' ' ) { st++; }
236                        if( JSON_TRIM.indexOf(ch) >= 0 || ch < ' ' ) { st++; }          // 8.1.1.0 (2022/02/04)
237                        else { break; }
238                }
239                while( st < ed ) {
240                        final char ch = str.charAt(ed-1);
241//                      if( ch == '"' || ch == '{'  || ch == '}' || ch == ' ' ) { ed--; }
242                        if( JSON_TRIM.indexOf(ch) >= 0 || ch < ' ' ) { ed--; }          // 8.1.1.0 (2022/02/04)
243                        else { break; }
244                }
245                return st < ed ? str.substring(st, ed) : "" ;
246        }
247}