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