View Javadoc
1   package com.github.mygreen.splate;
2   
3   import java.io.ByteArrayOutputStream;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.nio.charset.StandardCharsets;
7   import java.security.MessageDigest;
8   import java.security.NoSuchAlgorithmException;
9   import java.util.concurrent.atomic.AtomicReference;
10  
11  import lombok.AccessLevel;
12  import lombok.NoArgsConstructor;
13  import lombok.NonNull;
14  
15  /**
16   * 2Way-SQL機能の中で提供されるユーティリティクラス。
17   * <p>MirageSQL/Seaser2からの持ち込みなので、既存のユーティリティクラスとは分けて定義する。</p>
18   *
19   * @version 0.2
20   * @author T.TSUCHIE
21   *
22   */
23  @NoArgsConstructor(access = AccessLevel.PRIVATE)
24  public class SqlUtils {
25  
26      /**
27       * 空の文字列の配列です。
28       */
29      public static final String[] EMPTY_STRINGS = new String[0];
30  
31      /**
32       * 文字列を置き換えます。
33       * 置換対象の文字列がnullの場合は、結果として {@literal null} を返します。
34       *
35       * @param text テキスト
36       * @param fromText 置き換え対象のテキスト
37       * @param toText 置き換えるテキスト
38       * @return 結果
39       */
40      public static final String replace(final String text, final String fromText, final String toText) {
41  
42          if (text == null || fromText == null || toText == null) {
43              return null;
44          }
45          StringBuilder buf = new StringBuilder(100);
46          int pos2 = 0;
47          while (true) {
48              int pos = text.indexOf(fromText, pos2);
49              if (pos == 0) {
50                  buf.append(toText);
51                  pos2 = fromText.length();
52              } else if (pos > 0) {
53                  buf.append(text, pos2, pos);
54                  buf.append(toText);
55                  pos2 = pos + fromText.length();
56              } else {
57                  buf.append(text.substring(pos2));
58                  break;
59              }
60          }
61          return buf.toString();
62      }
63  
64      /**
65       * 文字列が空かどうか判定します。
66       *
67       * @param text 文字列
68       * @return 文字列が {@literal null} または空文字列なら {@literal true} を返します。
69       */
70      public static final boolean isEmpty(final String text) {
71          return text == null || text.length() == 0;
72      }
73  
74      /**
75       * リソースをテキストとして読み込む。
76       * <p>引数で指定したストリームは自動的にクローズします。</p>
77       *
78       * @param in リソース
79       * @param encoding エンコーディング
80       * @return 読み込んだテキスト
81       * @throws IOException リソースの読み込みに失敗した場合にスローされます。
82       */
83      public static String readStream(final InputStream in, final String encoding) throws IOException {
84  
85          ByteArrayOutputStream out = new ByteArrayOutputStream();
86          try(in; out) {
87              byte[] buf = new byte[1024 * 8];
88              int length = 0;
89              while((length = in.read(buf)) != -1){
90                  out.write(buf, 0, length);
91              }
92  
93              return new String(out.toByteArray(), encoding);
94  
95          }
96      }
97  
98      /**
99       * 文字列のメッセージダイジェストを作成します。
100      * @param text 計算対象の文字列
101      * @return メッセージダイジェスト
102      */
103     public static String getMessageDigest(final String text) {
104 
105         try {
106             MessageDigest md = MessageDigest.getInstance("SHA-256");
107             md.update(text.getBytes(StandardCharsets.UTF_8));
108 
109             byte[] hash = md.digest();
110             StringBuilder sb = new StringBuilder();
111             for (byte b : hash) {
112                 sb.append(String.format("%02x", b));
113             }
114 
115             return sb.toString();
116 
117         } catch (NoSuchAlgorithmException e) {
118             throw new IllegalArgumentException("not such algorithm name.", e);
119         }
120 
121     }
122 
123     /**
124      * SQL中の位置として行、列の位置を解決します。
125      * @param sql SQL
126      * @param position 位置
127      * @return テンプレートの位置情報
128      */
129     public static Position resolveSqlPosition(final @NonNull String sql, final int position) {
130 
131         // 現在の位置より前の情報を切り出し、改行を検索する。
132         final String before = sql.substring(0, position);
133 
134         int row = 1;
135 
136         final String[] searchWords = {"\r\n", "\r", "\n"};
137 
138         AtomicReference<CharSequence> foundStr = new AtomicReference<>();
139         int lastIndex = 0;
140         int index = 0;
141         while((index = indexOfAny(before, index, foundStr, searchWords)) >= 0) {
142             // 改行が見つかったら行数をカウントアップする。
143             row++;
144 
145             index += foundStr.get().length();
146 
147             // 現在見つかっている改行の位置を保持しておく
148             lastIndex = index;
149         }
150 
151         // 最後に改行が見つかった位置から、SQLの位置を引けば列がわかる
152         int col = position - lastIndex;
153         if(lastIndex > 0) {
154             col--;
155         }
156 
157         // 行の切り出し
158         final String after = sql.substring(lastIndex);
159         String line = after;
160         if(after != null && (index = indexOfAny(after, 0, null, searchWords)) >= 0) {
161             line = after.substring(0, index);
162         }
163 
164         final Position result = new Position();
165         result.setCol(col);
166         result.setRow(row);
167         result.setLine(line);
168 
169         return result;
170 
171     }
172 
173     /**
174      * <p>指定した複数の文字列から、最初に出現する位置のインデックスを返します。</p>
175      * <p>CommonsLangの{@code StringUtils#indexOfAny}の持ち込み。</p>
176      *
177      * <ul>
178      *   <li>検索対象の文字が {@code null}の場合は、{@code -1} を返します。</li>
179      *   <li>検索文字が {@code null} または空の配列の場合は、{@code -1} を返します。</li>
180      *   <li>検索文字に 空文字({@code ""})が含まれる場合、{@code 0}を返します。</li>
181      *   <li></li>
182      * </ul>
183      *
184      * @param str 検索対象の文字列
185      * @param startPos 検索開始する位置。0から始まります。
186      * @param foundStr 見つかった文字列。見つかった場合には値がセットされます。{@code null}の場合は値はセットされません。
187      * @param searchStrs  検索する文字列
188      * @return 初めに見つかった文字列のインデクスを返します。見つからない場合は、{@code -1}を返します。
189      * @since 0.2
190      */
191     public static int indexOfAny(final CharSequence str, final int startPos,
192             final AtomicReference<CharSequence> foundStr, final CharSequence... searchStrs) {
193 
194         if (str == null || searchStrs == null || searchStrs.length == 0) {
195             return INDEX_NOT_FOUND;
196         }
197 
198         // String's can't have a MAX_VALUEth index.
199         int ret = Integer.MAX_VALUE;
200 
201         int tmp = 0;
202         for (final CharSequence search : searchStrs) {
203             if (search == null) {
204                 continue;
205             }
206             tmp = indexOf(str, search, startPos);
207             if (tmp == INDEX_NOT_FOUND) {
208                 continue;
209             }
210 
211             if (tmp < ret) {
212                 ret = tmp;
213 
214                 // 見つかった文字列を保存する
215                 if(foundStr != null) {
216                     foundStr.set(search);
217                 }
218             }
219         }
220 
221         return ret == Integer.MAX_VALUE ? INDEX_NOT_FOUND : ret;
222     }
223 
224     /**
225      * 文字列の位置検索で失敗したときの値.
226      * @since 0.2
227      */
228     private static final int INDEX_NOT_FOUND = -1;
229 
230     /**
231      * {@link CharSequence}に対する {@code indexOf(CharSequence)}の実装。
232      * <p>CommonsLangの{@code CharSequenceUtils#indexOf}の持ち込み。</p>
233      *
234      * @param cs the {@code CharSequence} to be processed
235      * @param searchChar the {@code CharSequence} to be searched for
236      * @param start the start index
237      * @return the index where the search sequence was found
238      * @since 0.2
239      */
240     private static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
241         if (cs instanceof String) {
242             return ((String) cs).indexOf(searchChar.toString(), start);
243         } else if (cs instanceof StringBuilder) {
244             return ((StringBuilder) cs).indexOf(searchChar.toString(), start);
245         } else if (cs instanceof StringBuffer) {
246             return ((StringBuffer) cs).indexOf(searchChar.toString(), start);
247         }
248         return cs.toString().indexOf(searchChar.toString(), start);
249     }
250 }