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.db;
017
018import java.io.UnsupportedEncodingException;
019import java.util.Calendar;                                                      // 7.0.5.0 (2019/09/16)
020
021import org.opengion.fukurou.util.StringUtil;            // 6.9.8.0 (2018/05/28)
022import org.opengion.fukurou.util.HybsDateUtil;          // 7.0.5.0 (2019/09/16)
023
024import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;              // 7.2.9.5 (2020/11/28)
025
026/**
027 * JavaDB(derby) や、hsqldb に対する、Javaの拡張組込み関数です。
028 *
029 * staticメソッドとして、関数を定義します。引数や返り値は、各データベースの
030 * 定義に準拠します。
031 *
032 * <pre>
033 * ① JavaDB の場合
034 * 【概要】
035 *     実行するデータベースから見えるところに、ファイルを配置する必要があります。
036 *     java8 までなら、Javaのエクステンション(JAVA_HOME\)jre\lib\ext などですが、
037 *     java9以降は、CLASSPATH に設定します。
038 *     openGionでは、bin/const.bat で、OG_CLASSPATH 環境変数にパスを通して、
039 *     使用しています。
040 *     標準の Java staticメソッドを FUNCTION 定義することも出来ます。
041 * 【設定】
042 *     JavaDBに FUNCTION を定義します。(サンプル)
043 *      DROP FUNCTION TO_CHAR;
044 *
045 *      CREATE FUNCTION TO_CHAR ( VAL DOUBLE )
046 *      RETURNS VARCHAR(20)
047 *      DETERMINISTIC           -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
048 *      PARAMETER STYLE JAVA    -- 戻り値のタイプ
049 *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
050 *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.toChar' ;
051 *
052 * ② HSQLDB の場合
053 * 【概要】
054 *
055 * </pre>
056 *
057 * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にパッケージ変更
058 * @og.group 拡張組込み関数
059 *
060 * @version  1.1.0
061 * @author       Kazuhiko Hasegawa
062 * @since    JDK8.0,
063 */
064public final class Functions {
065    private static final String ENCODE = "UTF-8";
066
067        /**
068         * デフォルトコンストラクターをprivateにして、
069         * オブジェクトの生成をさせないようにする。
070         *
071         * @og.rev 6.9.7.0 (2018/05/14) 新規作成
072         */
073        private Functions() {}
074
075        /**
076         * 数値を文字列に変換します。
077         *
078         * この関数は、引数の double が、小数点を含まない場合は、
079         * 小数点以下を出しません。
080         * JavaDBの場合、数字と文字列の連結が出来ないため、文字列変換関数を用意します。
081         *
082         *      DROP FUNCTION TO_CHAR;
083         *
084         *      CREATE FUNCTION TO_CHAR ( VAL DOUBLE )
085         *      RETURNS VARCHAR(20)
086         *      DETERMINISTIC           -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
087         *      PARAMETER STYLE JAVA    -- 戻り値のタイプ
088         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
089         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.toChar' ;
090         *
091         * @og.rev 6.7.3.0 (2017/01/27) 新規作成
092         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にパッケージ変更
093         * @og.rev 6.9.8.0 (2018/05/28) FindBugs:浮動小数点の等価性のためのテスト
094         *
095         * @param       val     文字列に変換すべき数値
096         * @return      変換した文字列
097         */
098        public static String toChar( final double val ) {
099                //  6.9.8.0 (2018/05/28) FindBugs の警告を避ける方法が、見つかりませんでした。
100                return val == (int)val ? String.valueOf( (int)val ) : String.valueOf( val );
101
102//              final int intVal = (int)val;
103//              return ((double)intVal) == val ? String.valueOf( intVal ) : String.valueOf( val );
104        }
105
106        /**
107         * 特殊な文字列の連結を行います。
108         *
109         * これは、第1引数の数字と、第2、第3、第4の文字列をスペースで連結した文字列を返します。
110         * 引数の個数が、可変に出来ないため、完全に決め打ちです。
111         *
112         *      DROP FUNCTION JOIN2;
113         *
114         *      CREATE FUNCTION JOIN2 ( INTEGER , VARCHAR(2000) , VARCHAR(2000) , VARCHAR(2000) )
115         *      RETURNS VARCHAR(4000)
116         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
117         *      PARAMETER STYLE JAVA    -- 戻り値のタイプ
118         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
119         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.join2' ;
120         *
121         * @og.rev 6.7.3.0 (2017/01/27) 新規作成
122         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にパッケージ変更
123         *
124         * @param       no              第1引数の数字
125         * @param       arg2    第2引数
126         * @param       arg3    第3引数
127         * @param       arg4    第4引数
128         * @return      連結したした文字列
129         */
130        public static String join2( final int no , final String arg2 , final String arg3 , final String arg4 ) {
131                return new StringBuilder( BUFFER_MIDDLE )
132                                .append( no ).append( ' ' )
133                                .append( arg2 == null ? "" : arg2 ).append( ' ' )
134                                .append( arg3 == null ? "" : arg3 ).append( ' ' )
135                                .append( arg4 == null ? "" : arg4 )
136                                .toString() ;
137        }
138
139        /**
140         * 連結文字列を指定した、文字列の連結を行います。
141         *
142         * これは、第1引数の連結文字列を使用して、第2、第3、第4の文字列を連結します。
143         * 引数の個数が、可変に出来ないため、第2、第3、第4の文字列が、null か、ゼロ文字列の
144         * 場合は、連結せずにパスします。
145         *
146         *      DROP FUNCTION JOIN3;
147         *
148         *      CREATE FUNCTION JOIN3 ( VARCHAR(10) , VARCHAR(2000) , VARCHAR(2000) , VARCHAR(2000) )
149         *      RETURNS VARCHAR(4000)
150         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
151         *      PARAMETER STYLE JAVA    -- 戻り値のタイプ
152         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
153         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.join3' ;
154         *
155         * @og.rev 8.0.2.0 (2021/11/30) 新規作成
156         *
157         * @param       sep             連結時の文字列
158         * @param       arg2    第2引数
159         * @param       arg3    第3引数
160         * @param       arg4    第4引数
161         * @return      連結したした文字列
162         */
163        public static String join3( final String sep , final String arg2 , final String arg3 , final String arg4 ) {
164                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) ;
165
166                boolean useSep = false;         // 連結文字列を使うかどうか。直前の文字列が null 等の場合は、連結しません。
167                if( arg2 != null && !arg2.isEmpty() ) {
168                        buf.append( arg2 );
169                        useSep = true;
170                }
171
172                if( arg3 != null && !arg3.isEmpty() ) {
173                        if( useSep ) { buf.append( sep ); }
174                        buf.append( arg3 );
175                        useSep = true;
176                }
177
178                if( arg4 != null && !arg4.isEmpty() ) {
179                        if( useSep ) { buf.append( sep ); }
180                        buf.append( arg4 );
181                }
182
183                return buf.toString() ;
184        }
185
186        /**
187         * 対象の文字列の部分文字列を置換します。
188         *
189         * ただし、source、target、replacement のどれかが、null(ゼロ文字列)の場合は、
190         * 処理を実行せず、source をそのまま返します。
191         *
192         *      DROP FUNCTION REPLACE;
193         *
194         *      CREATE FUNCTION REPLACE ( VARCHAR(2000) , VARCHAR(2000) , VARCHAR(2000) )
195         *      RETURNS VARCHAR(4000)
196         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
197         *      PARAMETER STYLE JAVA    -- 戻り値のタイプ
198         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
199         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.replace' ;
200         *
201         * @og.rev 6.7.3.0 (2017/01/27) 新規作成
202         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にパッケージ変更
203         * @og.rev 6.9.8.0 (2018/05/28) source、target、replacement のどれかが、null(ゼロ文字列)の場合は、source を返す。
204         *
205         * @param       source 対象の文字列
206         * @param       target 置換したい文字列
207         * @param       replacement 置換する文字列
208         * @return      置換した文字列。
209         */
210        public static String replace( final String source , final String target , final String replacement ) {
211                // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要
212                // 6.9.8.0 (2018/05/28) source、target、replacement のどれかが、null(ゼロ文字列)の場合は、source を返す。
213                return StringUtil.isEmpty( source , target , replacement )              // 一つでも、null の場合、true
214                                        ? source
215                                        : source.replace( target,replacement );
216
217//              // 6.9.8.0 (2018/05/28) source、target、replacement のどれかが、null(ゼロ文字列)の場合は、source を返す。
218//              if( StringUtil.isEmpty( source , target , replacement ) ) {             // 一つでも、null の場合、true
219//                      return source;
220//              }
221//              else {
222//                      return source.replace( target,replacement );
223//              }
224
225//              if( source != null && target != null || !target.isEmpty() && replacement != null && !replacement.isEmpty() ) {
226//                      return source.replace( target,replacement );
227//              }
228//              else {
229//                      return source;
230//              }
231        }
232
233//      /**
234//       * この文字列内にあるすべてのoldCharをnewCharに置換した結果生成される文字列を返します。
235//       *
236//       * String#replace( char , char )を、繰り返します。
237//       *
238//       * @og.rev 6.7.9.0 (2017/04/28) 新規作成
239//       *
240//       * @param       source 対象の文字列
241//       * @param       target 以前の文字の集合
242//       * @param       replacement 置換する文字の集合
243//       * @return      置換した文字列。
244//       */
245//      public static String translate( final String source , final String target , final String replacement ) {
246//              String rtn = source ;
247//
248//              if( source != null && target != null && replacement != null && target.length() == replacement.length() ) {
249//                      for( int i=0; i<target.length(); i++ ) {
250//                              rtn = rtn.replace( target.charAt(i) , replacement.charAt(i) );
251//                      }
252//              }
253//
254//              return rtn;
255//      }
256
257        /**
258         * substr関数のバイト数版
259         * 過去に、hsqldb 用に作成したJava関数です。
260         *
261         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.hsqldb → org.opengion.fukurou.db にパッケージ変更
262         * ※ 現在未使用
263         *
264         * @param       value           変換する文字列
265         * @param       start           変換開始アドレス
266         * @param       length          変換バイト数
267         * @return      変換後文字列
268         * @throws      UnsupportedEncodingException    文字のエンコーディングがサポートされていません。
269         */
270        public static String substrb( final String value, final int start, final int length ) throws UnsupportedEncodingException {
271                String rtn = null;
272                final byte[] byteValue = makeByte( value );
273                if( byteValue != null ) {
274                        rtn = new String( byteValue,start-1,length,ENCODE );
275                }
276                return rtn;
277        }
278
279        /**
280         * length関数のバイト数版
281         * 過去に、hsqldb 用に作成したJava関数です。
282         *
283         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.hsqldb → org.opengion.fukurou.db にパッケージ変更
284         * ※ 現在未使用
285         *
286         * @param       value           バイト数をカウントする文字列
287         * @return      バイト数
288         * @throws      UnsupportedEncodingException    文字のエンコーディングがサポートされていません。
289         */
290        public static int lengthb( final String value ) throws UnsupportedEncodingException {
291                return makeByte( value ).length;
292        }
293
294        /**
295         * 指定の文字列をバイトコードに変換します。
296         * 引数の文字列が null の場合は、return は、byte[0] を返します。
297         *
298         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.hsqldb → org.opengion.fukurou.db にパッケージ変更
299         *
300         * @param       value   変換するストリング値
301         * @return      変換後文字列
302         * @throws      UnsupportedEncodingException エンコードが不正な場合
303         */
304        private static byte[] makeByte( final String value ) throws UnsupportedEncodingException {
305                byte[] rtnByte = new byte[0];
306                if( value != null ) {
307                        rtnByte = value.getBytes( ENCODE );
308                }
309                return rtnByte;
310        }
311
312        /**
313         * 日時文字列(yyyyMMddHHmmss)の引数1,2に対して、差分の範囲判定を行います。
314         *
315         * 範囲判定を行う引数 sec1、sec2、sec3 引数には、それぞれ(秒)レベルの値を指定します。
316         * 上記の引数の値が、0 の場合は、判定を回避します。
317         *
318         * sec1引数と比べて、小さければ 0 を、大きければ 1 を返します。
319         * sec2引数と比べて、小さければ sec1引数の結果を、大きければ 2 を返します。
320         * sec3引数と比べて、小さければ sec2引数の結果を、大きければ 3 を返します。
321         *
322         * 通常、sec1、sec2、sec3 と、順番に大きな値にしておきます。
323         * 小さいと判定された時点で、判定処理は終了します。
324         *
325         * date2 - date1 &lt;= sec1 ⇒ 0
326         *               &gt;  sec1 ⇒ 1
327         *               &gt;  sec2 ⇒ 2
328         *               &gt;  sec3 ⇒ 3
329         *
330         * date1,date2 のどちらかが、null,ゼロ文字列の場合は、判定しません。(return "0")
331         *
332         * @og.rev 7.0.5.0 (2019/09/16) 新規作成
333         * ※ 現在未使用
334         *
335         * @param       date1   前比較日時文字列(yyyyMMddHHmmss)
336         * @param       date2   後比較日時文字列(yyyyMMddHHmmss)
337         * @param       sec1    比較する秒
338         * @param       sec2    比較する秒
339         * @param       sec3    比較する秒
340         * @return      判定結果
341         */
342        public static String checkDelay( final String date1 , final String date2 , final double sec1 , final double sec2 , final double sec3 ) {
343                if( StringUtil.isNull( date1,date2 ) ) { return "0"; }          // どちらかが、null,ゼロ文字列の場合は、判定しません。
344
345                final Calendar cal1 = HybsDateUtil.getCalendar( date1 );
346                final Calendar cal2 = HybsDateUtil.getCalendar( date2 );
347
348                final double diff = ( cal2.getTimeInMillis() - cal1.getTimeInMillis() )/1000.0;                 // 秒に変換しておきます。
349
350                String rtn = "0";
351                if( sec1 > 0.0 ) {
352                        if( diff <= sec1 ) { return rtn; }
353                        else { rtn = "1"; }
354                }
355
356                if( sec2 > 0.0 ) {
357                        if( diff <= sec2 ) { return rtn; }
358                        else { rtn = "2"; }
359                }
360
361                if( sec3 > 0.0 ) {
362                        if( diff <= sec3 ) { return rtn; }
363                        else { rtn = "3"; }
364                }
365
366                return rtn;
367        }
368
369        /**
370         * 部分文字列の出現位置を検索します。
371         * 第1引数の検索元の文字列と、第2引数の部分文字列を指定します。
372         * 戻り値は、Javaの indexOf とは異なり、+1 された値が返ります。
373         *
374         *  DROP FUNCTION INSTR;
375         *
376         *  CREATE FUNCTION INSTR ( VARCHAR(1000) , VARCHAR(100) )
377         *  RETURNS INTEGER
378         *  DETERMINISTIC                       -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
379         *  PARAMETER STYLE JAVA        -- 戻り値のタイプ
380         *  NO SQL LANGUAGE JAVA        -- 関数の中でSQLは実行しないことを示す
381         *  EXTERNAL NAME 'org.opengion.fukurou.db.Functions.instr' ;
382         *
383         * @og.rev 7.3.0.0 (2021/01/06) 新規追加
384         *
385         * @param       value   検索元の文字列
386         * @param       sub             部分文字列
387         * @return      文字列が見つかった位置(最初が1、見つからなければ 0 )
388         */
389        public static int instr( final String value,final String sub ) {
390                // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要
391                return ( value == null || value.isEmpty() )
392                                        ? 0
393                                        : value.indexOf( sub ) + 1 ;
394
395//              if( value == null || value.isEmpty() ) {
396//                      return 0 ;
397//              }
398//              else {
399//                      return value.indexOf( sub ) + 1 ;
400//              }
401        }
402
403        /**
404         * 文字列を連結します。
405         *
406         * 文字列が、NULL の場合は、ゼロ文字列として扱います。
407         * 引数がどちらも NULL の場合は、ゼロ文字列が戻されます。
408         * これは、ORACLEとの互換性を考慮した関数です。
409         * ORACLE は、NULLとゼロ文字列を同等にNULLとして扱うため、
410         * COALESCE( VAL1,'' ) で、VAL1がNULLか、ゼロ文字列の場合は、NULLが返されます。
411         * NULLを含むWHERE条件で、WHERE COALESCE( VAL1,'' ) = COALESCE( VAL2,'' )
412         * としても、一致しないため、WHERE COALESCE( VAL1,'x' ) = COALESCE( VAL2,'x' )
413         * とする必要がありますが、VAL1が、実際の 'x' で、VAL2がNULLの場合もマッチします。
414         * 一方、Derbyでは、NULLとゼロ文字列を区別するため、COALESCE( VAL1,'x' ) の
415         * VAL1の値が、NULLとゼロ文字列で、結果が変わってしまいます。
416         * また、|| による文字列連結も、ORACLEとDerbyでは結果が変わるため、
417         * CONCAT関数を作成して、ORACLEと同じSQL文での比較ができるようにしておきます。
418         *
419         *  DROP FUNCTION CONCAT;
420         *
421         *  CREATE FUNCTION CONCAT ( VARCHAR(1000) , VARCHAR(1000) )
422         *  RETURNS VARCHAR(2000)
423         *  DETERMINISTIC                       -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
424         *  PARAMETER STYLE JAVA        -- 戻り値のタイプ
425         *  NO SQL LANGUAGE JAVA        -- 関数の中でSQLは実行しないことを示す
426         *  EXTERNAL NAME 'org.opengion.fukurou.db.Functions.concat' ;
427         *
428         * @og.rev 7.3.0.0 (2021/01/06) 新規追加
429         *
430         * @param       val1    文字列1
431         * @param       val2    文字列2
432         * @return      文字列1と文字列2を連結した文字列
433         */
434        public static String concat( final String val1,final String val2 ) {
435                return new StringBuilder( BUFFER_MIDDLE )
436                                .append( val1 == null ? "" : val1 )
437                                .append( val2 == null ? "" : val2 )
438                                .toString() ;
439        }
440
441        /**
442         * NULL文字列の判定変換を行います。
443         *
444         * 文字列が、NULL(または空文字列)は、文字列2を、NULLでない場合は、文字列1を返します。
445         * Derbyでは、NULLと空文字列が区別されますが、ORACLE等では、区別されないため、
446         * この関数でも区別しません。
447         * 空文字列の場合は、NULLと同じになります。
448         *
449         *  DROP FUNCTION NVL2;
450         *
451         *  CREATE FUNCTION NVL2 ( VARCHAR(1000) , VARCHAR(1000) , VARCHAR(1000) )
452         *  RETURNS VARCHAR(1000)
453         *  DETERMINISTIC                       -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
454         *  PARAMETER STYLE JAVA        -- 戻り値のタイプ
455         *  NO SQL LANGUAGE JAVA        -- 関数の中でSQLは実行しないことを示す
456         *  EXTERNAL NAME 'org.opengion.fukurou.db.Functions.nvl2' ;
457         *
458         * @og.rev 7.3.1.3 (2021/03/09) 新規追加
459         *
460         * @param       inval   判定する文字列
461         * @param       val1    invalがNULL(または空文字列)でない場合に返す文字列1
462         * @param       val2    invalがNULL(または空文字列)の場合に返す文字列2
463         * @return      文字列がNULLの場合は、val2 を、そうでない場合は、val1 を返す。
464         */
465        public static String nvl2( final String inval,final String val1,final String val2 ) {
466                return inval == null || inval.isEmpty() ? val2 : val1 ;
467        }
468}