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}